возник интересный вопрос: как вы оформляете код на plain C, который проверяет возвращаемый код из цепочки вызываемых функций?
Я в своей практике встречал следующие варианты:
1. "Ёлочка":
int f()
{
int failed;
void *obj1;
void *obj2;
failed = construct_obj1(&obj1);
if (!failed) {
failed = construct_obj2(&obj2);
if (!failed) {
failed = do_something(obj1, obj2);
// Обработка результатов вызова do_something
destruct_obj2(obj2);
}
destruct_obj1(obj1);
}
return failed;
}
Плюсы:
выделена основная ветка кода;
при добавлении в цепочку новых вызовов необходимо вносить минимум изменений;
Минусы:
код интенсивно уползает вправо;
если необходимо выйти из функции не через конечный return, то приходится вручную вызывать все деструкторы;
2. "goto в конец функции":
int f()
{
int failed;
void *obj1;
void *obj2;
failed = construct_obj1(&obj1);
if (failed) {
goto end1;
}
failed = construct_obj2(&obj2);
if (failed) {
goto end2;
}
failed = do_something(obj1, obj2);
// Обработка результатов вызова do_something
destruct_obj2(obj2);
end2:
destruct_obj1(obj1);
end1:
return res;
}
Плюсы:
код не уползает вправо
Минусы:
нелюбимый многими goto
при добавлении в цепочку новых вызовов можно легко забыть добавить деструктор или переход на нужную метку;
если необходимо выйти из функции не через конечный return, то приходится вручную вызывать все деструкты;
3. "выходим сразу":
int f()
{
int failed;
void *obj1;
void *obj2;
failed = construct_obj1(&obj1);
if (failed) {
return failed;
}
failed = construct_obj2(&obj2);
if (failed) {
destruct_obj1(obj1);
return failed;
}
failed = do_something(obj1, obj2);
// Обработка результатов вызова do_something
destruct_obj2(obj2);
destruct_obj1(obj1);
return failed;
}
Плюсы:
код не уползает вправо
Минусы:
при добавлении в цепочку новых вызовов приходится добавлять деструкторы как минимум в 2 места;
4. "выделение деструкторов в отдельный блок":
void finallize_f(void* obj1, void* obj2)
{
if (obj2 != NULL) {
destruct_obj2(obj2);
}
if (obj1 != NULL) {
destruct_obj1(obj1);
}
}
int f()
{
int failed;
void *obj1 = NULL;
void *obj2 = NULL;
failed = construct_obj1(&obj1);
if (failed) {
finallize_f(obj1, obj2);
return failed;
}
failed = construct_obj2(&obj2);
if (failed) {
finallize_f(obj1, obj2);
return failed;
}
failed = do_something(obj1, obj2);
// Обработка результатов вызова do_something
finallize_f(obj1, obj2);
return failed;
}
Плюсы:
код не уползает вправо
разрушение объектов сосредоточено в одном месте, из функции f можно корректно выйти из любой точки — достаточно вызвать finallize_f;
Коммент по поводу пункта 2.
P>2. "goto в конец функции": P>
.....
P>
P>Плюсы: P>
P>код не уползает вправо P>P>Минусы: P>
P>нелюбимый многими goto
Его не любят за примение не по делу. Если же его применять с умом, то ничего в нем крамольного нет.
P>при добавлении в цепочку новых вызовов можно легко забыть добавить деструктор или переход на нужную метку;
Чушь. Суть данного способа в том, что программа последовательно захватывает ресурсы (создает объекты), а потом освобождает их в обратном порядке. Если ты добавляешь захват нового ресурса, то все, что нужно сделать — вставить код его осводождения в нужное место в цепочке деструкторов.
P>если необходимо выйти из функции не через конечный return, то приходится вручную вызывать все деструкты;
Еще бОльшая чушь. Просто должна быть метка перед началом блока деструкторов. При необходимости выйти через конечный return выполняется переход на эту метку. P>
Подобная схема применяется в коде ядра linux-а, и никто вроде особо не жаловался...
P>3. "выходим сразу": P>
.................
P>
P>Плюсы: P>
P>код не уползает вправо P>P>Минусы: P>
P>при добавлении в цепочку новых вызовов приходится добавлять деструкторы как минимум в 2 места;
Вот уж тут точно можно забыть что-то освободить... P>
Остойный вариант. Годится только для небольшого числа используемых "ресурсов" (один-два)
P>4. "выделение деструкторов в отдельный блок": P>
..........
P>
То же самое, что и вариант 3. Чуть облагороженный.
возник интересный вопрос: как вы оформляете код на plain C, который проверяет возвращаемый код из цепочки вызываемых функций?
P>Я в своей практике встречал следующие варианты:
еще один вариант — "липовый" do {}while(0);
int f()
{
int failed;
void *obj1=NULL;
void *obj2=NULL;
do {
if (!(failed=construct_obj1(&obj1)))
break;
if (!(failed=construct_obj2(&obj2)))
break;
failed=do_something(obj1,obj2);
} while(false);
if (obj1!=NULL)
destruct_obj1(obj1);
if (obj2!=NULL)
destruct_obj2(obj1);
return failed;
}
плюсы :
+ в любом месте можно выйти из него простым break не заботясь о вызове деструкторов
+ все деструкторы вызываются в одном месте
+ несмотря на похожесть на вариант с goto тут нет меток
+ внутри do {} while() можно завести какой локальный объект внутри тела блока.
минусы :
— кому то может показаться что do {} while() используется не по делу)
— код все таки уползает вправо хотя и медленно.
— проверки на NULL режут глаз
Здравствуйте, TheCat, Вы писали:
TC>еще один вариант — "липовый" do {}while(0); TC>int f() TC>{ TC> int failed; TC> void *obj1=NULL; TC> void *obj2=NULL; TC> do { TC> if (!(failed=construct_obj1(&obj1))) TC> break; TC> if (!(failed=construct_obj2(&obj2))) TC> break; TC> failed=do_something(obj1,obj2); TC> } while(false);
TC> if (obj1!=NULL) TC> destruct_obj1(obj1); TC> if (obj2!=NULL) TC> destruct_obj2(obj1); TC> return failed; TC>}
TC>плюсы : TC> + в любом месте можно выйти из него простым break не заботясь о вызове деструкторов TC> + все деструкторы вызываются в одном месте TC> + несмотря на похожесть на вариант с goto тут нет меток TC> + внутри do {} while() можно завести какой локальный объект внутри тела блока. TC> минусы : TC> — кому то может показаться что do {} while() используется не по делу) TC> — код все таки уползает вправо хотя и медленно. TC> — проверки на NULL режут глаз
— если внутри есть еще цикл или case, то выход затруднителен... надо goto пользовать...
TC>еще один вариант — "липовый" do {}while(0); TC>плюсы : TC> + в любом месте можно выйти из него простым break не заботясь о вызове деструкторов TC> + все деструкторы вызываются в одном месте TC> + несмотря на похожесть на вариант с goto тут нет меток TC> + внутри do {} while() можно завести какой локальный объект внутри тела блока. TC> минусы : TC> — кому то может показаться что do {} while() используется не по делу) TC> — код все таки уползает вправо хотя и медленно. TC> — проверки на NULL режут глаз
для устранения минуса №3 можно воспользоваться следующей техникой:
int f()
{
int stage, failed;
void *obj1;
void *obj2;
for( stage = 0;; ) {
if( !(failed=construct_obj1(&obj1)) )
break;
++stage;
if( !(failed=construct_obj2(&obj2)) )
break;
++stage;
if( !(failed=do_something(obj1,obj2)) )
break;
++stage;
break;
}
switch( stage ) {
case 3: break;
case 2: destruct_obj2(obj1);
case 1: destruct_obj1(obj1);
}
return failed;
}
плюсы:
+ красиво
+ мы не обязаны инициализировать указатели на создаваемые объекты
минусы:
— нужна специальная переменная stage
— отслеживание значений переменной stage в каждой точке инициализации
Здравствуйте, barmafon, Вы писали:
TC>>еще один вариант — "липовый" do {}while(0); TC>>плюсы : TC>> + в любом месте можно выйти из него простым break не заботясь о вызове деструкторов TC>> + все деструкторы вызываются в одном месте TC>> + несмотря на похожесть на вариант с goto тут нет меток TC>> + внутри do {} while() можно завести какой локальный объект внутри тела блока. TC>> минусы : TC>> — кому то может показаться что do {} while() используется не по делу) TC>> — код все таки уползает вправо хотя и медленно. TC>> — проверки на NULL режут глаз
B>для устранения минуса №3 можно воспользоваться следующей техникой: B>
B>плюсы: B>+ красиво B>+ мы не обязаны инициализировать указатели на создаваемые объекты B>минусы: B>- нужна специальная переменная stage B>- отслеживание значений переменной stage в каждой точке инициализации
RB>Подобная схема применяется в коде ядра linux-а, и никто вроде особо не жаловался...
Там это используется по большей части потому, что эти конструкции генерируют наиболее оптимальный код на x86. О чем в этих самых исходниках и написано.
__>>Сами макросы : __>>[c]
TC>ТОЛЬКО НЕ ЭТО!!!))) TC>все что угодно только не макросы! TC>#define NULL 0L TC>я еще могу принять но END_ERROR() это уже перебор. TC>лучше уж тогда goto.)))
А чем вам так не нравятся макросы ?
Главное осторожно использовать и все будет в порядке.