Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Understanding Temporary Objects
Recognize when the compiler creates unnamed temporary objects and their lifetime.
Temporary Class Objects
A temporary object is an object created for a single expression and destroyed immediately after. Understanding temporaries helps you write efficient code and avoid subtle bugs.
What are temporary objects?
Temporaries are unnamed objects created on-the-fly:
#include <iostream>
class Value
{
public:
int num{};
Value(int n) : num{ n }
{
std::cout << "Created: " << num << '\n';
}
~Value()
{
std::cout << "Destroyed: " << num << '\n';
}
};
int calculate(Value v)
{
return v.num * 2;
}
int main()
{
int result{ calculate(Value{5}) }; // Value{5} is temporary
return 0;
}
Output:
Created: 5
Destroyed: 5
Value{5} creates a temporary object that's destroyed after calculate() returns.
When temporaries are created
Function arguments:
void process(Point p);
process(Point{10, 20}); // Temporary Point
Return values:
Point createPoint()
{
return Point{5, 5}; // Temporary returned
}
Point p{ createPoint() };
Type conversions:
class Distance
{
public:
double meters{};
Distance(double m) : meters{ m } {}
};
void printDistance(Distance d)
{
std::cout << d.meters << "m\n";
}
int main()
{
printDistance(10.5); // Temporary created from double
return 0;
}
Expressions:
class Temperature
{
public:
double celsius{};
Temperature(double c) : celsius{ c } {}
Temperature operator+(const Temperature& other) const
{
return Temperature{ celsius + other.celsius }; // Temporary
}
};
Lifetime of temporaries
Temporaries live until the end of the full expression that created them. A full expression is an expression that is not a subexpression of another expression.
Temporary objects are rvalues. This means they can only be used where rvalue expressions are accepted. You cannot take a non-const reference to a temporary, but you can bind a const reference to one (which extends its lifetime).
#include <iostream>
class Demo
{
public:
int id{};
Demo(int i) : id{ i }
{
std::cout << "Created " << id << '\n';
}
~Demo()
{
std::cout << "Destroyed " << id << '\n';
}
};
int main()
{
std::cout << "Start\n";
Demo{1};
std::cout << "Middle\n";
Demo{2};
std::cout << "End\n";
return 0;
}
Output:
Start
Created 1
Destroyed 1
Middle
Created 2
Destroyed 2
End
Each temporary is destroyed immediately.
Lifetime extension
When a temporary binds to a const reference, its lifetime extends:
#include <iostream>
class Data
{
public:
int value{};
Data(int v) : value{ v }
{
std::cout << "Created\n";
}
~Data()
{
std::cout << "Destroyed\n";
}
};
int main()
{
std::cout << "Before\n";
const Data& ref{ Data{42} }; // Temporary's lifetime extended
std::cout << "Middle\n";
std::cout << ref.value << '\n';
std::cout << "End\n";
// Temporary destroyed here when ref goes out of scope
return 0;
}
Output:
Before
Created
Middle
42
End
Destroyed
The temporary lives as long as ref exists.
Dangers with temporaries
Dangling references:
const Player& getDefaultPlayer()
{
return Player{"Hero", 100}; // Returns reference to temporary!
}
int main()
{
const Player& p{ getDefaultPlayer() }; // Dangling reference!
// p refers to destroyed object
return 0;
}
The temporary is destroyed when getDefaultPlayer() returns, leaving p referring to a destroyed object.
Non-const references don't extend lifetime:
Player& p{ Player{"Hero", 100} }; // Error: can't bind temporary to non-const ref
When temporaries cannot be used
Because temporaries are rvalues, they cannot be used where an lvalue is required:
void incrementValue(int& value) // Requires lvalue (non-const reference)
{
++value;
}
int main()
{
int x{ 5 };
incrementValue(x); // OK: x is an lvalue
incrementValue(5 + 3); // Error: 5 + 3 produces a temporary (rvalue)
return 0;
}
If you need to pass a value that will be modified, you must use a named variable.
Avoiding unnecessary temporaries
Pass by const reference:
// Creates temporary copy
void process(Player p);
// No temporary needed
void process(const Player& p);
Use move semantics (advanced):
void process(Player&& p); // Accepts temporaries efficiently
Return by value for small objects:
Point createPoint()
{
return Point{1, 2}; // Compiler optimizes this (RVO/NRVO)
}
Modern compilers eliminate the temporary through Return Value Optimization.
Practical example
class Matrix
{
public:
int data[100]{};
Matrix operator+(const Matrix& other) const
{
Matrix result{};
for (int i{}; i < 100; ++i)
result.data[i] = data[i] + other.data[i];
return result; // Temporary created
}
};
int main()
{
Matrix a{}, b{}, c{};
Matrix result{ a + b + c }; // Multiple temporaries created
return 0;
}
a + b creates a temporary. That temporary + c creates another. Compiler optimizations minimize this overhead.
Avoid creating temporaries unnecessarily by passing large objects by const reference. Don't return references to temporaries - always return by value if you're creating a new object. Use const references to extend lifetime when needed, but be careful about scope. Trust the compiler as modern compilers optimize temporary creation through copy elision and RVO.
Summary
Temporary objects: Temporaries are unnamed objects created for a single expression and destroyed immediately after, typically created during function calls, returns, type conversions, and expressions.
Lifetime: Temporaries live until the end of the statement that created them, unless their lifetime is extended by binding to a const reference.
Lifetime extension: When a temporary binds to a const reference, its lifetime extends to match the reference's scope, allowing safe temporary use.
Dangling references: Returning references to temporaries creates dangling references because the temporary is destroyed when the function returns, leaving the reference pointing to destroyed memory.
Avoiding unnecessary temporaries: Pass large objects by const reference instead of by value to avoid creating temporary copies during function calls.
Compiler optimizations: Modern compilers use Return Value Optimization (RVO) and copy elision to eliminate many temporary objects, making return-by-value efficient.
Temporary objects are a natural part of C++ expression evaluation. While they enable clean syntax and functional programming patterns, understanding their lifetime rules is critical for avoiding dangling references and writing efficient code. Trust compiler optimizations but remain aware of when temporaries are created.
Understanding Temporary Objects - Quiz
Test your understanding of the lesson.
Practice Exercises
Temporary Objects
Understand how temporary objects are created and used in expressions.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!