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

Last updated Jan 1, 2003.

Temporary objects (or temporaries, for short) differ in several aspects from ordinary objects with respect to both their lifetime and assignment. In this section, I will explain why and when temporary objects are introduced, and how to minimize their creation (and thus optimize your code).

Calling All Temps

The notion of temporary objects isn't new. It dates back to the early days of FORTRAN, when programmers were able (for the first time) to incorporate expressions that looked like mathematical formulae in their programs.

Things haven't changed much since then; in the evaluation of complex expressions, the compiler has to store intermediary results in a temporary location. Here's an example of an expression that necessitates a temporary:

int n;
read(n);
int x= 2+(4/n); //necessitates temporaries 

The evaluation order of this expression is as follows: first, the result of 4/n is computed. Obviously, it has to be stored somewhere. Therefore, the compiler introduces a temp int for storing it. Next, the expression 2+temp is evaluated and stored in another temporary object. Finally, the second temporary is assigned to x. Note that clever compilers can elide the second temporary by writing the result directly into x. However, eliding the first temporary isn't always possible.

You can break this complex expression into several steps. This way, instead of letting the compiler introduce temporaries, you manage them explicitly:

int temp = 2/n;
int x=2+temp;

In terms of performance and size, there's no difference between the two forms. However, when you let the compiler introduce temporaries as it sees fit, it can apply certain optimizations. For example, if n's value is known at compile-time, the expression 4/n can be calculated at compile-time, so the entire expression can be replaced with the result.

Temporaries' Anonymity

One characteristic of temporaries is that they are nameless. As such, you can't refer to them explicitly, nor can you assign a value to them. You may, however, copy their values to a named object:

int func(); //result is stored in a temporary
int c= func(); //copying temporary's value to c

The function call returns a temporary int whose value is copied into a named int called c. However, you can't access the temp variable directly:

func()=5; //error, can't assign to a temp

This is the major difference between temporaries and named objects. A temporary is an r-value, i.e., something that may appear on the right-hand side of an assignment expression, whereas a named object is an l-value, which is a piece of memory to which you can write a value.

Temporaries' Lifetime

Temporaries differ from ordinary objects with respect to their lifetime. When you define an auto object, it remains a live until its scope is exited. For example:

void func()
{
 int n=5;
 if (something)
 //..
 else..
 //..
}//n is destroyed here

By contrast, temporaries are destroyed as the last step in evaluating the full-expression that contains the point where they were created. This standardese phrasing means that temporaries are destroyed when control reaches the terminating semicolon of the full-expression in which they were created.

In the following example, the temporary string object remains alive conceptually until the first semicolon that terminates the entire expression:

string s1,s2,s3;
string s = 
       s3+(s2+s1)
       ; //temporaries destroyed at this point

Let's evaluate it. First, a temporary string is introduced which stores the value of s1+s2. This temporary is then added to s3. The result is then assigned to s. Finally, all the temporaries that have been introduced in the evaluation of this line of code are destroyed in reverse order of the completion of their construction.

Notice that the Standard guarantees the automatic destruction of temporaries at this point even when an exception is thrown during the stack unwinding process.

There is one exception to this rule, though. When you bind a reference to a temporary, the bound temporary persists for the lifetime of the reference. For example:

int main()
{
 string s1;
 const string& sr = string("a")+string("b"); //#1
 string s2;
}

The expression string("a")+string("b") in the line marked #1 creates three temporary string objects. The first temporary t1 contains the result of the subexpression string("a"), the second temporary t2 stores string("") and finally, a third temporary t3 stores the result of the addition of these two expressions. The order of the construction of the first two temporaries is unspecified because the compiler is allowed to evaluate the expression string("a")+string("b") in either direction. t1 and t2 are destroyed at the end of the full-expression, as usual. However, temporary t3 bound to the reference sr is destroyed at the end of sr's lifetime—that is, at the end of the program. This exception is meant to guarantee that sr doesn't turn silently into a dangling reference.

Summary

Temporary objects are usually harmless, and in some cases are unavoidable. However, when you deal with complex objects whose construction and destruction are expensive in performance terms, you should minimize their introduction.

Several guidelines can help you. First, break complex expressions into autonomous subexpressions, and store their results in a named object. If you need to store multiple intermediary results sequentially, you can reuse the same object. This is more efficient than letting the complier introduce a new temporary in every subexpression. In the following example, a single string object is used for storing two intermediate values:

//original expression was : string s= s1+s2+s3;
string temp=s1+s2;
temp+=s3;
string s=temp;

Another useful technique is to use += for self-assignment instead of +:

//original expression was : temp=temp+s3
temp+=s3;

Remember that the expression temp+s3 yields another temporary object; by using += you avoid this.