所以问题是这些实现中哪一个具有更好的性能和可读性。
想象一下,你必须编写一个代码,每个步骤都依赖于前一个步骤的成功,例如:
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 :(得分: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 ++代码中,第二个函数由于额外的跳转而非常糟糕,人们希望编译器能够将其与其他函数相同。但我怀疑任何真实的代码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
我挑战你发现不同功能之间的标签名称之外的区别。
答案 1 :(得分:2)
我认为性能(很可能甚至是二进制代码)与任何现代编译器都是一样的。
可读性在某种程度上与惯例和习惯有关。
我个人更喜欢第一种形式,可能你需要一个新功能来组合一些条件(我认为其中一些可以以一些有意义的方式组合在一起)。第三种形式对我来说看起来最神秘。
答案 2 :(得分:1)
由于C ++有RAII和自动清理功能,我倾向于选择拯救 - { - 1}} - 尽可能快的解决方案(你的第二个),因为代码变得更加清晰恕我直言。显然,这是一个意见,品味和YMMV的问题......