- Overview
-
Table of Contents
- Special Member Functions: Constructors, Destructors, and the Assignment Operator
- Operator Overloading
- Memory Management
- Templates
- Namespaces
- Time and Date Library
- Streams
- Object-Oriented Programming and Design Principles
- The Standard Template Library (STL) and Generic Programming
- Exception Handling
- Runtime Type Information (RTTI)
- Signal Processing
- Creating Persistent Objects
- Bit Fields
- New Cast Operators
- Environment Variables
- Variadic Functions
- Pointers to Functions
- Function Objects
- Pointers to Members
- Lock Files
- Design Patterns
- Dynamic Linking
-
Tips and Techniques
- Using the Swap() Algorithm
- Using class stopwatch for Performance Measurements
- Extending <tt><iostream></tt> to Support User-Defined Types
- Using <tt>auto_ptr</tt> To Automate Memory Management
- Using <tt>auto_ptr</tt> To Automate Memory Management, Part II
- Using <tt>auto_ptr</tt> To Automate Memory Management, Part III
- Using <tt>enum</tt>s as Mnemonic Indexes
- Create Objects on Pre-Allocated Memory Using <tt>Placement-new</tt>
- Online Books: <tt>Placement-new</tt>
- Bitwise Operators
- Bitwise Operators II
- Who's <tt>this</tt>?
- A Reference Guide
- The Virtues of Multiple Inheritance
- Interfaces
- Multiple Inheritance: Construction and Destruction Order
- nothrow new
- POD Initialization
- Object Initialization
- <tt>const</tt> Declarations
- The Semantics of <tt>volatile</tt>
- <tt>inline</tt> Functions
- Project Organization Guidelines
- All About <tt>bool</tt>
- <tt>typedef</tt> Declarations
- State of the <tt>union</tt>
- Dynamic Cast Uses
- Integrating C and C++
- <tt>const</tt>-Correctness
- <tt>const</tt>-Correctness: Advanced Issues
- Sprucing Up Legacy Code
- Virtual Constructors
- Naming Names
- Function Calls
- Speaking Standardese (updated)
- Speaking Standardese: the One Definition Rule
- Declarations and Definitions
- More on Declarations and Definitions
- The Most Vexing Parse
- Finally, At Last
- Sound Bytes (Admittedly Off Topic)
- Local Classes
- Complex Arithmetic
- Floating Point Woes
- String Manipulation
- The Object Model
- The Object Model II
- The Object Model III
- Temporary Objects
- Temporary Objects: Advanced Techniques
- Over-Engineering
- Security Enhancements
- Drop the (automatic) Pilot
- Choosing the Right Container
- Choosing the Right Container II
- Choosing the Right Container, Part III
- Arrays and Pointers
- Low-Level File I/O
- Low-Level File I/O Part II
- static Declarations, Part I
- static Declarations, Part II
- <code>static</code> Initialization Order
- Revisiting the Deprecation of File-Scope Static
- Virtual Memory and Memory Mapping
- Cellular Phone Programming Guidelines
- The Handle/Body Idiom
- Whole Program Optimization, Part I
- Whole Program Optimization, Part II
- Manipulating Directories
- Window Dressing
- <code>friend</code> Declarations
- <code>friend</code> Part II: the Interaction of Friendship and Template Classes
- Forcing Object Allocation on Specific Storage Types
- Lazy Evaluation
- Cache and Carry
- Controlling a Container’s Capacity
- Non-Blocking I/O, Part I
- Non-Blocking I/O, Part II
- Using Unions for Automatic Conversion
- Launching a Child Process
- <tt>switch</tt> Statements
- Introducing the "struct Hack"
- Scoped Enumerators
- Doing Statistics with STL
- Fixing the "Unresolved External" Linkage Error
- Understanding Calling Conventions
- Understanding the Empty Base Optimization
- Implementing RPC with the door Library, Part 1
- Implementing RPC with the door Library, Part 2
- Eliminating Two Common Pointer and <tt>sizeof</tt> Bugs
- Command Line Arguments
- Performance Myths Busting
- Tag Names And Types Part I
- Tag Names And Types Part II
- The Infamous goto
- Trimming Strings
- Can Objects Live Forever? Part I
- Can Objects Live Forever? Part II
- Five Ways to Improve Your Functions
- Member Aggregate Initialization
- Five Futile Coding-Style Debates
- The Good Parasite Idiom: An Exercise in OOD
- The Good Parasite Idiom: An Exercise in OOD, Part II
- The Good Parasite Idiom: An Exercise in OOD, Part III
- Ten Techniques to Reduce the Size of Your Classes, Part I
- Ten Techniques to Reduce the Size of Your Classes, Part II: Inheritance Issues
- Ten Techniques to Reduce the Size of Your Classes, Part III
- Ten Techniques to Reduce the Size of Your Classes IV
- Taking the Address of an Object with an Overloaded Operator <tt>&</tt>
- strcpy() -- How and Why Does It "Just Work"?
- Anonymous Structs
- Five Easy Ways to Reduce The Size of your Executables
- Standard Layout Classes and Trivially Copyable Types, Part I
- Standard Layout Classes and Trivially Copyable Types, Part II
- Five Simple Code Sanity Checks
- Five Things You Need to Know About C++11 Unions
- A Tour of C99
- A Tour of C1X
- C++0X: The New Face of Standard C++
- C++0x Concurrency
- The Reflecting Circle
- We Have Mail
- The Soapbox
- Numeric Types and Arithmetic
- Careers
- Locales and Internationalization
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 lifetimethat 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.
