Интересные ответы: хотя я и согласен со всеми из них (до сих пор), есть возможные коннотации к этому вопросу, которые до сих пор полностью игнорировались.
Если простой приведенный выше пример расширен за счет выделения ресурсови затем при проверке ошибок с возможным последующим высвобождением ресурсов картина может измениться.
Рассмотрим наивный подход Для начинающих может потребоваться:
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, предложил использовать break-меньше выключатель / чехол внутри блока захвата выше.Можно переключиться (typeof (e)) и затем выполнить вызовы free_resourcex()
, но это не тривиально и требует рассмотрения проекта .И помните, что переключатель / регистр без разрывов точно такой же, как goto с метками с гирляндными цепями ниже ...
Как отметил Марк B, в C ++ считается хорошим стилем следовать Resource AquisitionИнициализация принцип, 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Это неявные goto, такие как огромные, древовидные if / else, обработчики исключений, операторы loop / break / continue и т. д. И goto в приведенном выше примере считаются нормальными, так как они прыгают только на небольшое расстояние, имеют четкие меткии освободите код другого беспорядка для отслеживания условий ошибки. Этот вопрос также обсуждался здесь на stackoverflow .
Однако то, что отсутствует в последнем примере, - это хороший способ вернуть код ошибки.Я думал о добавлении result_code++
после каждого free_resource_x()
вызова и возврате этого кода, но это компенсирует некоторые выигрыши в скорости вышеописанного стиля кодирования.И трудно вернуть 0 в случае успеха.Может быть, я просто не обладаю воображением; -)
Итак, да, я думаю, что есть большая разница в вопросе кодирования преждевременных возвратов или нет.Но я также думаю, что это проявляется только в более сложном коде, который сложнее или невозможно реструктурировать и оптимизировать для компилятора.Что обычно происходит, когда в игру вступает распределение ресурсов.