gpt4 book ai didi

c++ - pIter != cont.end() 在 for 循环中的表现

转载 作者:太空宇宙 更新时间:2023-11-04 13:12:53 27 4
gpt4 key购买 nike

我最近正在学习 Herb Sutter 的“Exceptional C++”,我对他在第 6 项 - 临时对象中给出的特定建议深表怀疑。

他提出在以下代码中查找不必要的临时对象:

string FindAddr(list<Employee> emps, string name) 
{
for (list<Employee>::iterator i = emps.begin(); i != emps.end(); i++)
{
if( *i == name )
{
return i->addr;
}
}
return "";
}

作为示例之一,他建议在循环之前预先计算 emps.end() 的值,因为每次迭代都会创建一个临时对象:

For most containers (including list), calling end() returns a temporary object that must be constructed and destroyed. Because the value will not change, recomputing (and reconstructing and redestroying) it on every loop iteration is both needlessly inefficient and unaesthetic. The value should be computed only once, stored in a local object, and reused.

他建议用以下内容替换:

list<Employee>::const_iterator end(emps.end());
for (list<Employee>::const_iterator i = emps.begin(); i != end; ++i)

对我来说,这是不必要的并发症。即使用紧凑的 auto 替换丑陋的类型声明,他仍然得到两行代码而不是一行。更重要的是,他在外部作用域中有这个 end 变量。

我确信现代编译器无论如何都会优化这段代码,因为我实际上在这里使用了 const_iterator 并且很容易检查循环内容是否以某种方式访问​​容器。编译器在过去 13 年里变得更聪明了,对吧?

无论如何,在大多数情况下,我更喜欢带有 i != emps.end() 的第一个版本,因为我不太担心性能。但我想确定地知道,这是否是一种我可以依靠编译器来优化的构造?

更新

感谢您就如何改进这段无用的代码提出建议。请注意,我的问题是关于编译器,而不是编程技术。目前唯一相关的答案来自NPEEllioh .

最佳答案

UPD:如果我没有记错的话,您所说的这本书已于 1999 年出版。那是 14 年前的事了,而在现代编程中 14 年已经很长了。许多在 1999 年还不错和可靠的建议,现在可能已经完全过时了。虽然我的回答是关于单一编译器和单一平台,但还有一个更普遍的想法。

关心额外的变量、重用普通方法的返回值和旧 C++ 的类似技巧是向 1990 年代的 C++ 倒退一步。像 end() 这样的普通方法应该很好地内联,内联的结果应该作为调用它的代码的一部分进行优化。 99% 的情况根本不需要手动操作,例如创建 end 变量。只有在以下情况下才应该做这样的事情:

  1. 您知道,在您应该运行的某些编译器/平台上,代码没有得到很好的优化。
  2. 它已成为您程序中的瓶颈(“避免过早优化”)。

我查看了 64 位 g++ 生成的内容:

gcc version 4.6.3 20120918 (prerelease) (Ubuntu/Linaro 4.6.3-10ubuntu1)

一开始我觉得优化一下应该没问题,两个版本应该没什么区别。但看起来事情很奇怪:您认为非最佳的版本实际上更好。我认为,道德是:没有理由尝试比编译器更聪明。让我们看看这两个版本。

#include <list>

using namespace std;

int main() {
list<char> l;
l.push_back('a');

for(list<char>::iterator i=l.begin(); i != l.end(); i++)
;

return 0;
}

int main1() {
list<char> l;
l.push_back('a');
list<char>::iterator e=l.end();
for(list<char>::iterator i=l.begin(); i != e; i++)
;

return 0;
}

然后我们应该优化编译(我使用 64 位 g++,你可以试试你的编译器)并反汇编 mainmain1:

对于 main:

(gdb) disas main
Dump of assembler code for function main():
0x0000000000400650 <+0>: push %rbx
0x0000000000400651 <+1>: mov $0x18,%edi
0x0000000000400656 <+6>: sub $0x20,%rsp
0x000000000040065a <+10>: lea 0x10(%rsp),%rbx
0x000000000040065f <+15>: mov %rbx,0x10(%rsp)
0x0000000000400664 <+20>: mov %rbx,0x18(%rsp)
0x0000000000400669 <+25>: callq 0x400630 <_Znwm@plt>
0x000000000040066e <+30>: cmp $0xfffffffffffffff0,%rax
0x0000000000400672 <+34>: je 0x400678 <main()+40>
0x0000000000400674 <+36>: movb $0x61,0x10(%rax)
0x0000000000400678 <+40>: mov %rax,%rdi
0x000000000040067b <+43>: mov %rbx,%rsi
0x000000000040067e <+46>: callq 0x400610 <_ZNSt8__detail15_List_node_base7_M_hookEPS0_@plt>
0x0000000000400683 <+51>: mov 0x10(%rsp),%rax
0x0000000000400688 <+56>: cmp %rbx,%rax
0x000000000040068b <+59>: je 0x400698 <main()+72>
0x000000000040068d <+61>: nopl (%rax)
0x0000000000400690 <+64>: mov (%rax),%rax
0x0000000000400693 <+67>: cmp %rbx,%rax
0x0000000000400696 <+70>: jne 0x400690 <main()+64>
0x0000000000400698 <+72>: mov %rbx,%rdi
0x000000000040069b <+75>: callq 0x400840 <std::list<char, std::allocator<char> >::~list()>
0x00000000004006a0 <+80>: add $0x20,%rsp
0x00000000004006a4 <+84>: xor %eax,%eax
0x00000000004006a6 <+86>: pop %rbx
0x00000000004006a7 <+87>: retq

查看位于 0x0000000000400683-0x000000000040068b 的命令。那是循环体,它似乎是完美优化的:

   0x0000000000400690 <+64>:    mov    (%rax),%rax
0x0000000000400693 <+67>: cmp %rbx,%rax
0x0000000000400696 <+70>: jne 0x400690 <main()+64>

对于 main1:

(gdb) disas main1
Dump of assembler code for function main1():
0x00000000004007b0 <+0>: push %rbp
0x00000000004007b1 <+1>: mov $0x18,%edi
0x00000000004007b6 <+6>: push %rbx
0x00000000004007b7 <+7>: sub $0x18,%rsp
0x00000000004007bb <+11>: mov %rsp,%rbx
0x00000000004007be <+14>: mov %rsp,(%rsp)
0x00000000004007c2 <+18>: mov %rsp,0x8(%rsp)
0x00000000004007c7 <+23>: callq 0x400630 <_Znwm@plt>
0x00000000004007cc <+28>: cmp $0xfffffffffffffff0,%rax
0x00000000004007d0 <+32>: je 0x4007d6 <main1()+38>
0x00000000004007d2 <+34>: movb $0x61,0x10(%rax)
0x00000000004007d6 <+38>: mov %rax,%rdi
0x00000000004007d9 <+41>: mov %rsp,%rsi
0x00000000004007dc <+44>: callq 0x400610 <_ZNSt8__detail15_List_node_base7_M_hookEPS0_@plt>
0x00000000004007e1 <+49>: mov (%rsp),%rdi
0x00000000004007e5 <+53>: cmp %rbx,%rdi
0x00000000004007e8 <+56>: je 0x400818 <main1()+104>
0x00000000004007ea <+58>: mov %rdi,%rax
0x00000000004007ed <+61>: nopl (%rax)
0x00000000004007f0 <+64>: mov (%rax),%rax
0x00000000004007f3 <+67>: cmp %rbx,%rax
0x00000000004007f6 <+70>: jne 0x4007f0 <main1()+64>
0x00000000004007f8 <+72>: mov (%rdi),%rbp
0x00000000004007fb <+75>: callq 0x4005f0 <_ZdlPv@plt>
0x0000000000400800 <+80>: cmp %rbx,%rbp
0x0000000000400803 <+83>: je 0x400818 <main1()+104>
0x0000000000400805 <+85>: nopl (%rax)
0x0000000000400808 <+88>: mov %rbp,%rdi
0x000000000040080b <+91>: mov (%rdi),%rbp
0x000000000040080e <+94>: callq 0x4005f0 <_ZdlPv@plt>
0x0000000000400813 <+99>: cmp %rbx,%rbp
0x0000000000400816 <+102>: jne 0x400808 <main1()+88>
0x0000000000400818 <+104>: add $0x18,%rsp
0x000000000040081c <+108>: xor %eax,%eax
0x000000000040081e <+110>: pop %rbx
0x000000000040081f <+111>: pop %rbp
0x0000000000400820 <+112>: retq

循环的代码类似,就是:

   0x00000000004007f0 <+64>:    mov    (%rax),%rax
0x00000000004007f3 <+67>: cmp %rbx,%rax
0x00000000004007f6 <+70>: jne 0x4007f0 <main1()+64>

但是循环周围还有很多额外的东西。显然,额外的代码使事情变得更糟。

关于c++ - pIter != cont.end() 在 for 循环中的表现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38882625/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com