Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Named objects vs. temporary objects: Is it better to avoid named objects when possible?

Tags:

c++

The following is an excerpt I found from a coding style documentation for a library:

Where possible, it can be better to use a temporary rather than storing a named object, eg:

DoSomething( XName("blah") );

rather than

XName n("blah"); DoSomething( n );

as this makes it easier for the compiler to optimise the call, may reduce the stack size of the function, etc. Don't forget to consider the lifetime of the temporary, however.

Assuming the object does not need to be modified and lifetime issues are not a problem, is this guideline true? I was thinking that in this day and age it wouldn't make a difference. However, in some cases you couldn't avoid a named object:

XName n("blah");
// Do other stuff to mutate n
DoSomething( n );

Also, with move semantics, we can write code like this since the temporaries are eliminated:

std::string s1 = ...;
std::string s2 = ...;
std::string s3 = ...;
DoSomething( s1 + s2 + s3 );

rather than (I've heard that the compiler can optimize better with the following in C++03):

std::string s1 = ...;
std::string s2 = ...;
std::string s3 = ...;
s1 += s2; 
s1 += s3;  // Instead of s1 + s2 + s3
DoSomething(s1);

(Of course, the above may boil down to measure and see for yourself, but I was wondering if the general guideline mentioned above has any truth to it)

like image 663
Jesse Good Avatar asked Jul 04 '13 00:07

Jesse Good


People also ask

How can you extend the life time of an object?

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.

What are temporary objects C++?

A temporary object is an unnamed object created by the compiler to store a temporary value.

Where the temporary objects are created?

3. Where the temporary objects (created while return by value) are created? Explanation: The temporary object are created within the function and are intended to return the value for specific use. Either the object can be assigned to another object or be used directly if possible.

What is a temporary object in C++?

Meyer: "True temporary objects in C++ are invisible - they don't appear in your source code. They arise whenever a non-heap object is created but not named. Such unnamed objects usually arise in one of two situations: when implicit type conversions are applied to make function calls succeed and when functions return objects."

What are the types of temporary objects in SQL Server?

There are basically two types of temporary objects in SQL Server: local and global temporary objects. You can recognize temporary objects by the fact that their names begin with a hash (#). You can tell whether an object is local or global by whether it has one or two hash marks in front of it.

What happens to a temporary object when the connection is closed?

This means that if the connection in your program code, ETL package, SSMS or Data Studio is closed in which the temporary object was created, then this object is no longer available. It’s different with global objects: as long as a session exists that uses the object, it is kept alive.

What is the use of temporary objects in Linux?

Temporary objects can be useful for storing intermediate results in larger processes or for procedures that you do not want to use outside of a process. Of course, it is important to consider the scope of the object and to distinguish whether the object is also available outside the session.


1 Answers

The main job of the compiler frontend is to remove names from everything to resolve the underlying semantic structures.

Tending to avoid names does help avoid taking addresses of objects unnecessarily, which can unintuitively stop the compiler from manipulating data. But there are enough ways to get the address of a temporary that it's all but moot. And named objects are special in that they are not eligible for constructor elision in C++, but as you mention, move semantics eliminate most expensive unnecessary copy-construction.

Just focus on writing readable code.

Your first example does eliminate a copy of n, but in C++11 you can use move semantics instead: DoSomething( std::move( n ) ).

In the example s1 + s2 + s3, it's also true that C++11 makes things more efficient, but move semantics and elimination of temporaries are different things. A move constructor just makes construction of a temporary less expensive.

I was also under the misimpression that C++11 would eliminate the temporaries, as long as you used the idiom

// What you should use in C++03
foo operator + ( foo lhs, foo const & rhs ) { return lhs += rhs; }

This is actually untrue; lhs is a named object, not a temporary, and it is not eligible for the return value optimization form of copy elision. In fact, in C++11 this will produce a copy, not a move! You would need to fix this with std::move( lhs += rhs );.

// What you should use in C++11
foo operator + ( foo lhs, foo const & rhs ) { return std::move( lhs += rhs ); }

Your example uses std::string, not foo, and that operator+ is defined (essentially, and since C++03) as

// What the C++03 Standard Library uses
string operator + ( string const & lhs, string const & rhs )
    { return string( lhs ) += rhs; } // Returns rvalue expression, as if moved.

This strategy has similar properties to the above, because a temporary is disqualified for copy elision once it is bound to a reference. There are two potential fixes, which give a choice between speed and safety. Neither fix is compatible with the first idiom, which with move already implements the safe style (and as such is what you should use!).

Safe style.

Here there are no named objects, but the temporary bound to the lhs argument cannot be directly constructed into the result binding to a reference stops copy elision.

// What the C++11 Standard Library uses (in addition to the C++03 library style)
foo operator + ( foo && lhs, foo const & rhs )
    { return std::move( lhs += rhs ); }

Unsafe style.

A second overload accepting an rvalue reference and return the same reference eliminates the intermediate temporary completely (no reliance on elision), allowing a chain of + calls to be converted perfectly into += calls. But unfortunately it also disqualifies the remaining temporary at the start of the call chain from lifetime extension, by binding it to a reference. So the returned reference is valid until the semicolon, but then it's going away and nothing can stop it. So this is mainly useful inside something like a template expression library, with documented restrictions on what results can be bound to a local reference.

// No temporary, but don't bind this result to a local!
foo && operator + ( foo && lhs, foo const & rhs )
    { return std::move( lhs += rhs ); }

Evaluating library documentation as such requires a little bit of evaluation of the library authors' skill. If they say to do things a certain quirky way because it's always more efficient, be skeptical because C++ isn't purposely designed to be quirky, but it is designed to be efficient.

However in the case of expression templates where temporaries include complicated type computations which would be interrupted by assignment to a named variable of concrete type, you should absolutely listen to what the authors say. In such a case, they would be presumably much more knowledgeable.

like image 100
Potatoswatter Avatar answered Sep 27 '22 17:09

Potatoswatter