Преимущество заключается в том, что вам не нужно проверять код ошибки после каждого потенциально неудачного вызова. Однако, чтобы это работало, вам нужно объединить его с классами RAII, чтобы все автоматически очищалось при разматывании стека.
С сообщениями об ошибках:
int DoSomeThings()
{
int error = 0;
HandleA hA;
error = CreateAObject(&ha);
if (error)
goto cleanUpFailedA;
HandleB hB;
error = CreateBObjectWithA(hA, &hB);
if (error)
goto cleanUpFailedB;
HandleC hC;
error = CreateCObjectWithA(hB, &hC);
if (error)
goto cleanUpFailedC;
...
cleanUpFailedC:
DeleteCObject(hC);
cleanUpFailedB:
DeleteBObject(hB);
cleanUpFailedA:
DeleteAObject(hA);
return error;
}
с исключениями и RAII
void DoSomeThings()
{
RAIIHandleA hA = CreateAObject();
RAIIHandleB hB = CreateBObjectWithA(hA);
RAIIHandleC hC = CreateCObjectWithB(hB);
...
}
struct RAIIHandleA
{
HandleA Handle;
RAIIHandleA(HandleA handle) : Handle(handle) {}
~RAIIHandleA() { DeleteAObject(Handle); }
}
...
На первый взгляд версия RAII / Exceptions выглядит длиннее, пока вы не поймете, что код очистки нужно писать только один раз (и есть способы упростить это). Но вторая версия DoSomeThings намного понятнее и удобнее в обслуживании.
НЕ пытайтесь использовать исключения в C ++ без языка RAII, так как это приведет к утечке ресурсов и памяти. Вся ваша очистка должна быть сделана в деструкторах объектов, выделенных стеком.
Я понимаю, что есть другие способы обработки кода ошибки, но все они выглядят примерно одинаково. Если вы бросите gotos, вы в конечном итоге будете повторять код очистки.
Одним из пунктов кодов ошибок является то, что они делают очевидным, где что-то может выйти из строя и как оно может выйти из строя. В приведенном выше коде вы пишете его с предположением, что что-то не выйдет из строя (но если они это сделают, вы будете защищены оболочками RAII). Но в конечном итоге вы обращаете меньше внимания на то, где что-то может пойти не так.