Цікаві відповіді: Хоча я і погоджуюся з усіма ними (поки що), існують можливі відтінки цього питання, які дотепер повністю ігноруються.
Якщо простий приклад вище розширити за допомогою розподілу ресурсів, а потім перевірити помилки з можливим звільненням ресурсів, картина може змінитися.
Поміркуйте, що наївний підхід може сприйняти початківців:
int func(..some parameters...) {
res_a a = allocate_resource_a();
if (!a) {
return 1;
}
res_b b = allocate_resource_b();
if (!b) {
free_resource_a(a);
return 2;
}
res_c c = allocate_resource_c();
if (!c) {
free_resource_b(b);
free_resource_a(a);
return 3;
}
do_work();
free_resource_c(c);
free_resource_b(b);
free_resource_a(a);
return 0;
}
Сказане означало б крайній варіант стилю повернення передчасно. Зверніть увагу на те, як з часом код стає дуже повторюваним та нерентабельним, коли його складність зростає. Сьогодні люди можуть використовувати обробку винятків, щоб зловити їх.
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
try {
a = allocate_resource_a(); # throws ExceptionResA
b = allocate_resource_b(); # throws ExceptionResB
c = allocate_resource_c(); # throws ExceptionResC
do_work();
}
catch (ExceptionBase e) {
# Could use type of e here to distinguish and
# use different catch phrases here
# class ExceptionBase must be base class of ExceptionResA/B/C
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
throw e
}
return 0;
}
Філіп запропонував, подивившись приклад goto нижче, використовувати безперебійний перемикач / корпус всередині блоку catch вище. Можна переключити (typeof (e)), а потім пропустити free_resourcex()
дзвінки, але це нетривіально і потребує розгляду проекту . І пам’ятайте, що перемикач / чохол без перерв - це точно як гото з етикетками, пов’язаними з ромашками внизу ...
Як зазначив Марк Б, в C ++ вважається хорошим стилем дотримуватися принципу Придбання ресурсів - це ініціалізація , коротше RAII . Суть концепції полягає у використанні екземпляра об’єкта для отримання ресурсів. Потім ресурси автоматично звільняються, як тільки об’єкти виходять за межі обсягу та викликаються їх деструктори. Для взаємозалежних ресурсів слід бути особливо обережними, щоб забезпечити правильний порядок розташування та розробити типи об'єктів, щоб необхідні дані були доступні для всіх деструкторів.
Або в дні до винятку може зробити:
int func(..some parameters...) {
res_a a = allocate_resource_a();
res_b b = allocate_resource_b();
res_c c = allocate_resource_c();
if (a && b && c) {
do_work();
}
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
return 0;
}
Але цей надто спрощений приклад має кілька недоліків: його можна використовувати лише в тому випадку, якщо виділені ресурси не залежать один від одного (наприклад, його не вдалося використати для розподілу пам'яті, відкриття файлового файлу, а потім зчитування даних з ручки в пам'ять ), і він не надає індивідуальні, розрізнювальні коди помилок як значення повернення.
Щоб тримати код швидким (!), Компактним, легко читабельним та розширюваним, Лінус Торвальдс застосував інший стиль коду ядра, який має справу з ресурсами, навіть використовуючи сумнозвісний goto таким чином, що має абсолютно сенс :
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
a = allocate_resource_a() || goto error_a;
b = allocate_resource_b() || goto error_b;
c = allocate_resource_c() || goto error_c;
do_work();
error_c:
free_resource_c(c);
error_b:
free_resource_b(b);
error_a:
free_resource_a(a);
return 0;
}
Суть дискусії у списках розсилки ядра полягає в тому, що більшість мовних функцій, які "бажані" над оператором goto, є неявними готосами, такими як величезні, деревоподібні if / else, обробники винятків, операції циклу / перерви / продовження тощо І goto у наведеному вище прикладі вважаються нормальними, оскільки вони стрибають лише на невеликій відстані, мають чіткі мітки та звільняють код іншого безладу для відстеження умов помилок. Це питання також обговорювалося тут щодо stackoverflow .
Однак те, чого не вистачає в останньому прикладі, є приємним способом повернути код помилки. Я думав додати result_code++
після кожного free_resource_x()
дзвінка та повернути цей код, але це компенсує деякі збільшення швидкості вищезазначеного стилю кодування. І важко повернути 0 у разі успіху. Можливо, я просто не уявляю ;-)
Так, так, я думаю, що існує велика різниця у питанні кодування передчасних повернень чи ні. Але я також думаю, що це очевидно лише у складнішому коді, який важче або неможливо переструктурувати та оптимізувати для компілятора. Як правило, це відбувається, коли розподіл ресурсів починає діяти.