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