Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Temporary Objects: Advanced Techniques

Last updated Jan 1, 2003.

Temporary Objects: Advanced Techniques

Previously, I presented the basic concepts of temporary objects. Today, I explain under which circumstances the explicit instantiation of temporary objects may be useful, how it's actually done, and which loopholes to avoid.

Explicit Instantiation of Temporaries

The syntax for explicitly instantiating a temporary object looks confusingly similar to that of an ordinary auto object instantiation, except that in the case of a temporary object, you don't name the object. The following minimal pair shows the difference between the instantiation of a named object and a temporary object:

string s("hello"); //ordinary auto object
string ("hello"); //#2 nameless, hence: temporary
int x; //temporary created in #2 has been destroyed at this point

The temporary object remains alive until the end of the full expression in which it was created. In line #2 above, the temporary string object is destroyed immediately before the declaration of x. However, if you bind a reference to const to this temporary, it remains alive as long as the reference is alive:

string s("hello"); //ordinary auto object
const string &sr =string("hello"); // temp bound to reference to const
int x; //
sr.clear(); //fine, temporary is still alive

This technique is useful when the reference itself is used in the program rather than the bound object. Notice, however, that in pre-standard C++, it was possible to use this technique with references to non const objects as well:

string s("hello"); //ordinary auto object
string &sr =string("hello"); // illegal in ISO C++
int x; //
sr.clear(); 

Some compilers still accept this code. However, it's illegal and dangerous.

An explicit temporary instantiation is also useful when you need to pass an argument to a function. For example, in the following example, a temporary Date object is created as an argument passed to the push_back() member function. By using a temporary you ensure that the argument cannot be used elsewhere in the program:

vector <Date> vd;
vd.push_back(Date()); 

Explicit instantiation of a temporary may also be needed when you want to assign a new state to an existing object at once. Suppose you want to assign a new value to a complex number. You may use its setter member functions to assign each of its data members explicitly. Alternatively, you can use the following shortcut:

complex <double> d(1.0, 0.5);
//suppose you want to change d's value to (10, 0.9)
d=complex(10,0.9);

Here, a temporary complex object with the desired value is created and assigned to d. The full-blown instantiation of a complex object might seem inefficient. However, many compilers are clever enough to optimize this code and elide the actual construction of a temporary.

Secondly, this notation is shorter and more readable. In some cases, it is even more efficient than explicitly invoking a sequence of member functions that clear the current object's state and subsequently assign a new value to it.

Temporary Pointer Variables

A new expression can also create a temporary object, or more precisely -- a temporary pointer:

{
 new string; //creates a temp pointer 
}

Here, the temporary is the value of the pointer returned by new. The string object itself isn't temporary; it's an ordinary dynamically allocated object. Obviously, this line of code causes a memory leak. The address of the allocated object is stored in a temp object of type string * but it's discarded immediately. Hence, there's no way to refer the string objects after this expression has been fully-evaluated nor is there a way to destroy the allocated object.

Are such temporary pointers pure evil? Not quite. In some cases, they are rather useful. For example, you can use them to populate a container of pointers:

vector <string *> vpstr;
vpstr.push_back (new string);
//..add more pointers to the vector
for (int i=0;i<vpstr.size(); i++)//delete the pointers
 delete vpstr[i];

Similarly, temporary pointers can be useful when you create smart pointers:

std::tr1::shared_ptr <string> pstr(new string);

The Return Value Optimization

Although it isn't a requirement in C++, the Return Value Optimization (RVO) is a common optimization technique employed by many compilers. The RVO occurs when the return value of a function (which returns an object by value) is copied to another object. For example:

string getmagicword()
(
return string("Supercalifragilisticexpialidocious");
);
string magicw=getmagicword();

getmagicword() constructs a local string object. Next, this local object is copy-constructed on the caller's stack, making a temporary string object that is used as the argument of magicw's assignment operator/copy-constructor. Next, magicw's copy constructor executes, at last.

Yes, you heard me right: to initialize magicw, it takes no less than:

  • One constructor call for the local object inside getmagicword().
  • Two copy constructor calls: one for copying the local object onto the caller's stack frame, and then another call for copying that copy to magicw.
  • Two destructor calls: one for the object created inside getmagicword and one for the temp copied onto the caller's stack frame.

The performance overhead is overwhelming. Imagine what would happen if getmagicword() returned a vector by value.

C++ compilers can save much of this overhead by rewriting getmagicword() as:

void __getmagicword(string & str)
(
str="Supercalifragilisticexpialidocious";
);

and rewriting this definition:

string magicw=getmagicword();

as:

string magicw; 
__getmagicword(magicw);

This way, no temporary and local strings are needed.

Syntax Musings

An explicit instantiation of a temporary object always include an argument list. The list may be empty:

void func (const string & s);
func (string() ); // invoking string's default ctor

When the non-default constructor is to be invoked, the argument list of the temporary contains the constructor's arguments:

func (string("Test")); 

However, when you instantiate an ordinary automatic object, you have to distinguish syntactically between a default constructor invocation and a non-default constructor invocation. Here is how you create an auto string object without the constructor's arguments:

string s; //note: no parentheses in this case

By contrast, if you wish to pass arguments to the constructor, use parentheses:

string s("welcome!"); 

Using an empty argument list to instantiate an auto object is not a syntactic error -- it's much worse:

string s(); //compiles OK; expect a surprise 

This line of code doesn't instantiate an auto string object called s. Instead, it declares s as a function that takes no arguments and returns a string object by value. Compilers can't guess that the programmer actually meant to define an auto string object here rather than declare a function prototype. To overcome the syntactic ambiguity, C++ distinguishes between these two cases by omitting the parentheses when instantiating a named auto object.