Да, объединение двух ненавистных конструкций. Так ли это плохо, как кажется, или его можно рассматривать как хороший способ контролировать использование goto, а также обеспечить разумную стратегию очистки?
На работе у нас была дискуссия о том, разрешать ли goto в нашем стандарте кодирования. В общем, никто не хотел разрешать бесплатное использование goto, но некоторые положительно относились к его использованию для переходов очистки. Как в этом коде:
void func()
{
char* p1 = malloc(16);
if( !p1 )
goto cleanup;
char* p2 = malloc(16);
if( !p2 )
goto cleanup;
goto norm_cleanup;
err_cleanup:
if( p1 )
free(p1);
if( p2 )
free(p2);
norm_cleanup:
}
Вышеупомянутое преимущество такого использования заключается в том, что вам не нужно получать этот код:
void func()
{
char* p1 = malloc(16);
if( !p1 ){
return;
}
char* p2 = malloc(16);
if( !p2 ){
free(p1);
return;
}
char* p3 = malloc(16);
if( !p3 ){
free(p1);
free(p2);
return;
}
}
Особенно в конструктороподобных функциях с большим количеством аллокаций это иногда может стать очень плохим, не в последнюю очередь, когда кто-то должен что-то вставить посередине.
Итак, чтобы иметь возможность использовать goto, но при этом четко изолировать его от свободного использования, для обработки задачи был создан набор макросов управления потоком. Выглядит примерно так (упрощенно):
#define FAIL_SECTION_BEGIN int exit_code[GUID] = 0;
#define FAIL_SECTION_DO_EXIT_IF( cond, exitcode ) if(cond){exit_code[GUID] = exitcode; goto exit_label[GUID];}
#define FAIL_SECTION_ERROR_EXIT(code) exit_label[GUID]: if(exit_code[GUID]) int code = exit_code[GUID];else goto end_label[GUID]
#define FAIL_SECTION_END end_label[GUID]:
Мы можем использовать это следующим образом:
int func()
{
char* p1 = NULL;
char* p2 = NULL;
char* p3 = NULL;
FAIL_SECTION_BEGIN
{
p1 = malloc(16);
FAIL_SECTION_DO_EXIT_IF( !p1, -1 );
p2 = malloc(16);
FAIL_SECTION_DO_EXIT_IF( !p2, -1 );
p3 = malloc(16);
FAIL_SECTION_DO_EXIT_IF( !p3, -1 );
}
FAIL_SECTION_ERROR_EXIT( code )
{
if( p3 )
free(p3);
if( p2 )
free(p2);
if( p1 )
free(p1);
return code;
}
FAIL_SECTION_END
return 0;
Это выглядит красиво и имеет много преимуществ, НО есть ли недостатки, о которых нам следует подумать, прежде чем внедрять это в разработку? В конце концов, это очень контроль над потоком и goto:ish. Оба обескуражены. Каковы аргументы для их обескураживания в этом случае?
Спасибо.