Showing posts with label temporary objects. Show all posts
Showing posts with label temporary objects. Show all posts

Avoiding Temporary Objects

I was trying to find something in this very interesting book, titled "Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions" by Herb Sutter.

There was a chapter called "Temporary Objects". Curiosity got the better of me and I started reading. The book is extremely well written and the problem very well explained. Here is a summary of the Temporary Objects problem. Have a look at the book for complete details.

Consider the following peice of 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
"";
}

This short function has three unnecessary temporary objects, two subtler ones, and two red herrings.

The two more-obvious temporaries are buried in the function declaration itself:

string FindAddr( list emps, string name )

The parameters should be passed by const reference (const&) instead of by value. So they would now become const list& and const string&, respectively. Pass-by-value forces the compiler to make complete copies of both objects, which can be expensive and completely unnecessary.

The third more-obvious avoidable temporary occurs in the for loop's termination condition:

for( /*...*/ ; i != 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.

Next, lets consider the way we increment i in the for loop:

for( /*...*/ ; i++

Postincrement is usually less efficient than preincrement because it has to remember and return its original value. An example of Postincrement implementation is as follows:


const T T::operator++(int)
{

T old( *this ); // remember our original value
++*this; // always implement postincrement
// in terms of preincrement
return old; // return our original value
}
As you can see postincrement is less efficient than preincrement. Postincrement has to do all the same work as preincrement, but in addition it also has to construct and return another object containing the original value. Since the original value is never used in the code, there is no reason to use postincrement. Preincrement should be used instead.

You can continue reading this further in chapter 6 of the book. Book details as follows:


Copy constructor forms

The copy constructor can have both forms(*) as below:

    A(A& rhs);
    A(const A& rhs);

Both are perfectly valid and it is pretty easy to forget the 'const' for the rhs argument. The compiler accepts it as a perfectly valid syntax thinking that is what your intent was. But the above two are a bit different from each other. The first one rejects to work with/copy from parameters that are not const objects of type A as well as temporaries. So, if you tried something like this:

    const A const_a;
    A copy_const_a(const_a); //ERROR!
    A copy_from_temp(A(/*some arguments*/)); //ERROR!
    A copy_from_return_val(somefunc_return_A_by_val); //ERROR!
//any other forms that might want to create copies out of const or temorary objects

the compiler will just shout at you at the appropriateness of the copy constructor you wrote. And if you by any chance overlooked the argument for your it assuming it be correct, you would most certainly lose some of your precious time thinking probably some of your member objects are not copy constructible or there's some issue of that sort.

This brings us to the primary point of const correctness. Make things const, declare them const unless you explicitly wanted them to be non-const. I know some people who have the opinion that this should have been implicit by the language and to make something non-const there should have been a keyword i.e. there were a keyword 'mutable' (not in the sense that it is used in the language currently) or something similar to let know that the object is non-const. But it's probably too late for it. :-)


(*) Or for that matter any of the 4 forms as mentioned below:

    A(A& rhs /*, either no other args or all default args*/);
    A(const A& rhs /*, either no other args or all default args*/);
    A(volatile A& rhs /*, either no other args or all default args*/);
    A(const volatile A& rhs /*, either no other args or all default args*/);

Check out this stream