Эффективность по сравнению с блоком Do-While (false) и возвратом

Таким образом, вопрос в том, какая из этих реализаций имеет лучшую производительность и удобочитаемость.

Представьте, что вам нужно написать код, каждый шаг которого зависит от успеха предыдущего, например:

bool function()
{
bool isOk = false;

if( A.Func1() )
{
B.Func1();

if( C.Func2() )
{
if( D.Func3() )
{
...
isOk = true;
}
}
}

return isOk;
}

Допустим, есть до 6 вложенных IF, так как я не хочу, чтобы отступы слишком сильно увеличивались вправо, и я не хочу вкладывать вызовы функций, потому что задействовано несколько параметров, первый подход будет использовать обратная логика:

bool function()
{

if( ! A.Func1() ) return false:

B.Func1();

if( ! C.Func2() ) return false;

if( ! D.Func3() ) return false;
...

return true;
}

Но как насчет того, чтобы избежать такого большого количества возвратов, как это:

bool function()
{
bool isOk = false;

do
{
if( ! A.Func1() ) break:

B.Func1();

if( ! C.Func2() ) break;

if( ! D.Func3() ) break;
...
isOk = true;
break;

}while(false);

return isOk;
}

0

Решение

Компиляторы разбивают ваш код на простые инструкции, используя инструкции ветвления для формирования циклов, если / иначе и т. Д., И вряд ли ваш код будет вообще отличаться, как только компилятор пройдет через него.

Напишите код, который, по вашему мнению, наиболее подходит для решения, которое вам требуется.

Если бы я «проголосовал» за один из трех вариантов, я бы сказал, что мой код в основном является вариантом 2. Однако я не следую ему неукоснительно. Если имеет больше смысла (с точки зрения «как вы думаете об этом») писать в варианте 1, я сделаю это.

Я не думаю, что когда-либо писал или даже видел код, написанный как вариант 3 — я уверен, что это происходит, но если ваша цель — получить один возврат, то я бы сказал, что вариант 1 более понятен и более очевидный выбор. Вариант 3 на самом деле просто «Перейти к другому имени» (см. Мой самый вознагражденный ответ [и это после того, как у меня было что-то вроде 80 голосов вниз за предложение goto как решение]). Лично я не вижу вариант 3 лучше, чем два других, и если функция не достаточно коротка, чтобы увидеть, что делать и какое-то время на той же странице, вы также фактически не знаете, что она не будет зацикливаться без прокрутки. — что на самом деле не очень хорошая вещь.

Если после профилирования кода вы обнаружите, что определенная функция отнимает больше времени, чем вы считаете «правильным», изучите код сборки.

Просто чтобы проиллюстрировать это, я возьму ваш код и скомпилирую все три примера с g ++ и clang ++ и покажу полученный код. Вероятно, это займет несколько минут, потому что сначала мне нужно сделать его компилируемым.

Ваш источник, после некоторого массажа, чтобы он скомпилировался как отдельный исходный файл:

class X
{
public:
bool Func1();
bool Func2();
bool Func3();
};

X A, B, C, D;

bool function()
{
bool isOk = false;

if( A.Func1() )
{
B.Func1();

if( C.Func2() )
{
if( D.Func3() )
{
isOk = true;
}
}
}

return isOk;
}bool function2()
{

if( ! A.Func1() ) return false;

B.Func1();

if( ! C.Func2() ) return false;

if( ! D.Func3() ) return false;

return true;
}bool function3()
{
bool isOk = false;

do
{
if( ! A.Func1() ) break;

B.Func1();

if( ! C.Func2() ) break;

if( ! D.Func3() ) break;

isOk = true;
}while(false);

return isOk;
}

Код, сгенерированный clang 3.5 (скомпилирован из источников несколько дней назад):

_Z8functionv:                           # @_Z8functionv
pushq   %rax
movl    $A, %edi
callq   _ZN1X5Func1Ev
testb   %al, %al
je  .LBB0_2

movl    $B, %edi
callq   _ZN1X5Func1Ev
movl    $C, %edi
callq   _ZN1X5Func2Ev
testb   %al, %al
je  .LBB0_2

movl    $D, %edi
popq    %rax
jmp _ZN1X5Func3Ev           # TAILCALL

xorl    %eax, %eax
popq    %rdx
retq_Z9function2v:                          # @_Z9function2v
pushq   %rax
movl    $A, %edi
callq   _ZN1X5Func1Ev
testb   %al, %al
je  .LBB1_1

movl    $B, %edi
callq   _ZN1X5Func1Ev
movl    $C, %edi
callq   _ZN1X5Func2Ev
testb   %al, %al
je  .LBB1_3
movl    $D, %edi
callq   _ZN1X5Func3Ev
# kill: AL<def> AL<kill> EAX<def>
jmp .LBB1_5
.LBB1_1:
xorl    %eax, %eax
jmp .LBB1_5
.LBB1_3:
xorl    %eax, %eax
.LBB1_5:
# kill: AL<def> AL<kill> EAX<kill>
popq    %rdx
retq

_Z9function3v:                          # @_Z9function3v
pushq   %rax
.Ltmp4:
.cfi_def_cfa_offset 16
movl    $A, %edi
callq   _ZN1X5Func1Ev
testb   %al, %al
je  .LBB2_2

movl    $B, %edi
callq   _ZN1X5Func1Ev
movl    $C, %edi
callq   _ZN1X5Func2Ev
testb   %al, %al
je  .LBB2_2

movl    $D, %edi
popq    %rax
jmp _ZN1X5Func3Ev           # TAILCALL
.LBB2_2:
xorl    %eax, %eax
popq    %rdx
retq

В коде clang ++ вторая функция очень незначительно хуже из-за дополнительного скачка, который, как можно было бы надеяться, компилятор мог бы решить так же, как и у других. Но я сомневаюсь, что любой реалистичный код, где func1 а также func2 а также func3 на самом деле что-то значимое покажет какую-то ощутимую разницу.

И g ++ 4.8.2:

_Z8functionv:
subq    $8, %rsp
movl    $A, %edi
call    _ZN1X5Func1Ev
testb   %al, %al
jne .L10
.L3:
xorl    %eax, %eax
addq    $8, %rsp
ret
.L10:
movl    $B, %edi
call    _ZN1X5Func1Ev
movl    $C, %edi
call    _ZN1X5Func2Ev
testb   %al, %al
je  .L3
movl    $D, %edi
addq    $8, %rsp
jmp _ZN1X5Func3Ev

_Z9function2v:
subq    $8, %rsp
movl    $A, %edi
call    _ZN1X5Func1Ev
testb   %al, %al
jne .L19
.L13:
xorl    %eax, %eax
addq    $8, %rsp
ret
.L19:
movl    $B, %edi
call    _ZN1X5Func1Ev
movl    $C, %edi
call    _ZN1X5Func2Ev
testb   %al, %al
je  .L13
movl    $D, %edi
addq    $8, %rsp
jmp _ZN1X5Func3Ev

_Z9function3v:
.LFB2:
subq    $8, %rsp
movl    $A, %edi
call    _ZN1X5Func1Ev
testb   %al, %al
jne .L28
.L22:
xorl    %eax, %eax
addq    $8, %rsp
ret
.L28:
movl    $B, %edi
call    _ZN1X5Func1Ev
movl    $C, %edi
call    _ZN1X5Func2Ev
testb   %al, %al
je  .L22
movl    $D, %edi
addq    $8, %rsp
jmp _ZN1X5Func3Ev

Я призываю вас заметить разницу между именами ярлыков между различными функциями.

4

Другие решения

Я думаю, что производительность (и, скорее всего, даже двоичный код) будет одинаковой с любым современным компилятором.

Удобочитаемость — это вопрос соглашений и привычек.

Лично я предпочел бы первую форму, и, возможно, вам понадобится новая функция для группировки некоторых условий (я думаю, что некоторые из них могут быть сгруппированы каким-либо значимым образом). Третья форма выглядит наиболее загадочно для меня.

2

Поскольку в C ++ есть RAII и автоматическая очистка, я предпочитаю выход из кризисаreturn— как можно скорее (ваше второе), потому что код ИМХО становится чище. Очевидно, это вопрос мнения, вкуса и YMMV …

1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector