I was getting through "Exceptional C++" by Herb Sutter lately, and I have serious doubts about a particular recommendation he gives in Item 6 - Temporary Objects.
He offers to find unnecessary temporary objects in the following code:
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 "";
}
As one of the example, he recommends to precompute the value of emps.end()
before the loop, since there is a temporary object created on every iteration:
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.
And he suggests replacing by the following:
list<Employee>::const_iterator end(emps.end());
for (list<Employee>::const_iterator i = emps.begin(); i != end; ++i)
For me, this is unnecessary complication. Even if one replaces ugly type declarations with compact auto
, he still gets two lines of code instead of one. Even more, he has this end
variable in the outer scope.
I was sure modern compilers will optimize this piece of code anyway, because I'm actually using const_iterator
here and it is easy to check whether the loop content is accessing the container somehow. Compilers got smarter within the last 13 years, right?
Anyway, I will prefer the first version with i != emps.end()
in most cases, where I'm not so much worried about performance. But I want to know for sure, whether this is a kind of construction I could rely on a compiler to optimize?
Update
Thanks for your suggestions on how to make this useless code better. Please note, my question is about compiler, not programming techniques. The only relevant answers for now are from NPE and Ellioh.
UPD: The book you are speaking about has been published in 1999, unless I'm mistaking. That's 14 years ago, and in modern programming 14 years is a lot of time. Many recommendations that were good and reliable in 1999, may be completely obsolete by now. Though my answer is about a single compiler and a single platform, there is also a more general idea.
Caring about extra variables, reusing a return value of trivial methods and similar tricks of old C++ is a step back towards the C++ of 1990s. Trivial methods like
end()
should be inlined quite well, and the result of inlining should be optimized as a part of the code it is called from. 99% situations do not require manual actions such as creating anend
variable at all. Such things should be done only if:I've looked at what is generated by 64-bit g++:
Initially I thought that with optimizations on it should be ok and there should be no difference between two versions. But looks like things are strange: the version you considered non-optimal is actually better. I think, the moral is: there is no reason to try being smarter than a compiler. Let's see both versions.
Then we should compile this with optimizations on (I use 64-bit
g++
, you may try your compiler) and disassemblemain
andmain1
:For
main
:Look at the commands located at 0x0000000000400683-0x000000000040068b. That's the loop body and it seems to be perfectly optimized:
For
main1
:The code for the loop is similar, it is:
But there is alot of extra stuff around the loop. Apparently, extra code has made the things WORSE.