我遇到了一组用于C++ 咆哮的幻灯片.这里和那里有一些有趣的花絮,但是幻灯片8对我来说很突出.其内容大约是:
不断变化的风格
旧的和破坏的:
Run Code Online (Sandbox Code Playgroud)for (int i = 0; i < n; i++)新热度:
Run Code Online (Sandbox Code Playgroud)for (int i(0); i != n; ++i)
我之前从未见过for使用第二种形式的循环,所以声称它是"新热"让我感兴趣.我可以看到它的一些基本原理:
!= 可能比硬件更快 <++i不需要编译器保持旧的值i,这是i++可以做的.我认为这是不成熟的优化,因为现代优化编译器会将两者编译成相同的指令; 如果有的话,后者更糟糕,因为它不是一个"正常" for循环.使用!=而不是<对我来说也是可疑的,因为它使循环在语义上与原始版本不同,并且可能导致一些罕见但有趣的错误.
for循环的"新热度"版本是否受欢迎?这些天是否有任何理由使用该版本(2016+),例如不寻常的循环变量类型?
Bar*_*rry 74
使用构造函数和复制初始化直接初始化
这些与ints 完全相同,并将生成相同的代码.使用您喜欢阅读的任何一个或您的代码政策,等等.
!=可能比硬件更快<
生成的代码实际上不会i < nVS i != n,它会像i - n < 0VS i - n == 0.也就是说,你会jle在第一种情况下获得a ,je在第二种情况下获得a .所有jcc指令都具有相同的性能(请参阅指令集参考和选项参考,它们只是将所有jcc指令列在一起,因为吞吐量为0.5).
哪个更好?对于ints,可能与性能无关.
<如果你想跳过中间的元素,那么要非常安全,从那以后你不必担心最终会出现无限/未定义的循环.但是,只需编写使用您正在编写的循环编写最有意义的条件.另请查看dasblinkenlight的答案.
++i不需要编译器保持旧的值i,这是i++可以做的.
是的,这是胡说八道.如果你的编译器不能告诉你不需要旧的值而只是重写i++to ++i,那么就得到一个新的编译器.那些肯定会编译成具有相同性能的相同代码.
也就是说,使用正确的东西是一个很好的指导方针.你想增加i,所以那就是++i.只在需要使用后增量时才使用后增量.完全停止.
也就是说,真正的"新热"肯定是:
for (int i : range(n)) { ... }
Run Code Online (Sandbox Code Playgroud)
das*_*ght 22
你是正确的优化编译器和前缀与后缀++运算符.它对于一个并不重要int,但是当你使用迭代器时它更重要.
问题的第二部分更有趣:
使用
!=而不是<对我来说也是可疑的,因为它使循环在语义上与原始版本不同,并且可能导致一些罕见但有趣的错误.
我将它改写为"可以捕获一些罕见但有趣的错误."
Dijkstra在他的" 编程学科"一书中提出了一个简单的方法来论证这一点.他指出,推理后置条件较强的循环比推理后置条件较弱的循环更容易.由于循环的后置条件是其连续条件的倒数,因此应该优选具有较弱连续条件的循环.
a != b是弱a < b,因为a < b暗示a != b,但a != b并不意味着a < b.因此,a != b做出更好的延续条件.
简单来说,你知道a == b循环结束后会立即a != b结束; 另一方面,当循环a < b结束时,所有你知道的是a >= b,这不如知道确切的相等.
Rak*_*111 13
我个人不喜欢第二个,我会用:
for (int i = 0; i < n; ++i); //A combination of the two :)
Run Code Online (Sandbox Code Playgroud)
int i = 0 VS int i(0)没有任何区别,他们甚至编译成相同的汇编指令(没有优化):
int main()
{
int i = 0; //int i(0);
}
Run Code Online (Sandbox Code Playgroud)
int i = 0 版:
main:
pushq %rbp
movq %rsp, %rbp
movl $0, -4(%rbp)
movl $0, %eax
popq %rbp
ret
Run Code Online (Sandbox Code Playgroud)
int i(0) 版:
main:
pushq %rbp
movq %rsp, %rbp
movl $0, -4(%rbp)
movl $0, %eax
popq %rbp
ret
Run Code Online (Sandbox Code Playgroud)
i < n VS i != n你是对的,!=可能会引入一些有趣的错误:
for (int i = 0; i != 3; ++i)
{
if (i == 2)
i += 2; //Oops, infinite loop
//...
}
Run Code Online (Sandbox Code Playgroud)
的!=比较主要用于迭代器,这没有定义<(或>)运算符.也许这就是作者的意思?
但是在这里,第二个版本显然更好,因为它更清楚地表明了意图而不是另一个(并且引入了更少的错误).
i++ VS ++i对于内置类型(和其他普通类型),例如int,没有区别,因为编译器会优化临时返回值.在这里,一些迭代器也很昂贵,因此创建和破坏可能会影响性能.
但是,这真的不会在这种情况下无所谓,因为即使没有优化他们发出相同组件的输出!
int main()
{
int i = 0;
i++; //++i;
}
Run Code Online (Sandbox Code Playgroud)
i++ 版:
main:
pushq %rbp
movq %rsp, %rbp
movl $0, -4(%rbp)
addl $1, -4(%rbp)
movl $0, %eax
popq %rbp
ret
Run Code Online (Sandbox Code Playgroud)
++i 版:
main:
pushq %rbp
movq %rsp, %rbp
movl $0, -4(%rbp)
addl $1, -4(%rbp)
movl $0, %eax
popq %rbp
ret
Run Code Online (Sandbox Code Playgroud)
这两种形式与绩效无关.重要的是你如何编写代码.遵循类似的模式,专注于表达和简洁.因此,对于初始化,首选int i(0)(或更好:i {0}),因为这强调这是初始化,而不是赋值.对于比较,!=和<之间的区别在于您对类型的要求较低.对于整数没有区别,但总的来说迭代器可能不会支持少于,但应该始终支持相等.最后,前缀增量更好地表达了您的意图,因为您不使用结果.