Do-While(false)阻止vs返回的效率

时间:2014-04-04 23:32:11

标签: c++ design-patterns do-while control-flow

所以问题是这些实现中哪一个具有更好的性能和可读性。

想象一下,你必须编写一个代码,每个步骤都依赖于前一个步骤的成功,例如:

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;
}

3 个答案:

答案 0 :(得分:4)

编译器会将代码分解为简单的指令,使用分支指令形成循环,if / else等,并且一旦编译器完成它,代码就不可能完全不同。

编写您认为对所需解决方案最有意义的代码。

如果我去"投票"对于三种变体中的一种,我说我的代码主要是变体2.但是,我不会虔诚地遵循它。如果它更有意义(从#34;你如何看待它"观点)来编写变体1,那么我会这样做。

我不认为我曾经写过,或者甚至看过像变种3这样写的代码 - 我确信它会发生,但如果你的目标是获得一次回报,那么我&# 39; d说变体1是更明确和更明显的选择。 Variant 3实际上只是一个" goto的另一个名字" (请参阅我的most rewarded answer [以及在我提出goto作为解决方案之后我有80次下选票之后的事情)。我个人并不认为变体3比其他两个更好,除非功能足够短,看不到同时在同一页面上进行,你也实际上并不知道它赢了&#39 ; t循环没有滚动 - 这真的不是一件好事。

如果你那么,在对代码进行分析之后,发现一个特定的函数花费的时间比你想象的要多,#34;对",研究汇编代码。

为了说明这一点,我将使用您的代码并使用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 ++代码中,第二个函数由于额外的跳转而非常糟糕,人们希望编译器能够将其与其他函数相同。但我怀疑任何真实的代码func1func2func3实际上做了什么有意义的代码会显示出任何可衡量的差异。

和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

我挑战你发现不同功能之间的标签名称之外的区别。

答案 1 :(得分:2)

我认为性能(很可能甚至是二进制代码)与任何现代编译器都是一样的。

可读性在某种程度上与惯例和习惯有关。

我个人更喜欢第一种形式,可能你需要一个新功能来组合一些条件(我认为其中一些可以以一些有意义的方式组合在一起)。第三种形式对我来说看起来最神秘。

答案 2 :(得分:1)

由于C ++有RAII和自动清理功能,我倾向于选择拯救 - { - 1}} - 尽可能快的解决方案(你的第二个),因为代码变得更加清晰恕我直言。显然,这是一个意见,品味和YMMV的问题......