Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Optimizing Object Construction
Understand how compilers optimize away unnecessary copies through copy elision.
Class Initialization and Copy Elision
When you initialize objects, C++ provides optimization opportunities to eliminate unnecessary copies. Understanding these optimizations helps you write efficient code.
Copy elision
Copy elision is a compiler optimization that eliminates unnecessary copy operations:
class Heavy
{
public:
int data[1000]{};
Heavy()
{
std::cout << "Constructor\n";
}
Heavy(const Heavy&)
{
std::cout << "Copy constructor\n";
}
};
Heavy createHeavy()
{
return Heavy{}; // Might expect a copy here
}
int main()
{
Heavy obj{ createHeavy() }; // And another copy here
return 0;
}
Without optimization, you'd expect:
- Constructor (create temporary)
- Copy constructor (return from function)
- Copy constructor (initialize obj)
With copy elision, you might see just:
Constructor
The compiler eliminates both copies!
Return Value Optimization (RVO)
Return Value Optimization eliminates the copy when returning objects:
Point makePoint()
{
return Point{10, 20}; // Constructed directly in caller's memory
}
int main()
{
Point p{ makePoint() }; // No copy!
return 0;
}
The object is constructed directly where it will be used, skipping intermediate copies.
Named Return Value Optimization (NRVO)
NRVO optimizes returns of named objects:
Point makePoint(int x, int y)
{
Point result{x, y}; // Named local variable
return result; // NRVO can eliminate the copy
}
The compiler may construct result directly in the caller's memory space.
Guaranteed vs. optional elision
C++17 and later: Certain elisions are guaranteed:
Thing t{ Thing{} }; // Guaranteed: no copy/move
Thing t = Thing{}; // Guaranteed: no copy/move
Thing f();
Thing t{ f() }; // Guaranteed when f() returns prvalue
Other cases: Elision is optional (compiler-dependent):
Thing makeIt()
{
Thing local{};
return local; // NRVO is optional
}
Direct initialization vs. copy initialization
Direct initialization (preferred):
Point p1{5, 10}; // Direct initialization
Point p2{ makePoint() }; // Direct initialization
Copy initialization (can prevent elision):
Point p3 = Point{5, 10}; // Copy initialization (elision likely)
Point p4 = makePoint(); // Copy initialization (elision likely)
While copy elision makes these equivalent in practice, direct initialization is clearer about intent.
When elision doesn't happen
Returning different objects prevents NRVO:
Point makePoint(bool flag)
{
Point p1{1, 2};
Point p2{3, 4};
if (flag)
return p1; // Can't optimize: multiple return paths
else
return p2;
}
The compiler doesn't know which object to construct in the caller's memory.
Move semantics as fallback
When elision doesn't occur, C++ uses move construction if available:
class Resource
{
public:
int* data{};
Resource(int size)
{
data = new int[size]{};
}
// Copy constructor
Resource(const Resource& other)
{
std::cout << "Copy\n";
// ... deep copy ...
}
// Move constructor
Resource(Resource&& other) noexcept
{
std::cout << "Move\n";
data = other.data;
other.data = nullptr;
}
};
Resource makeResource()
{
Resource temp{100};
return temp; // Move instead of copy if elision doesn't happen
}
Forcing copies (defeating elision)
Sometimes you explicitly need a copy:
Point makePoint()
{
Point p{5, 10};
return Point{p}; // Explicit copy, prevents NRVO
}
But this is rarely necessary.
Practical implications
Don't avoid returning by value:
// Good: Return by value, trust optimization
Data createData()
{
return Data{params};
}
// Unnecessary: Trying to "optimize" what's already optimized
void createData(Data& out)
{
out = Data{params};
}
Don't use std::move on return:
// Wrong: Prevents RVO!
Thing makeIt()
{
Thing local{};
return std::move(local); // DON'T DO THIS
}
// Right: Let compiler optimize
Thing makeIt()
{
Thing local{};
return local; // Compiler handles this optimally
}
Observing elision
#include <iostream>
class Observer
{
public:
int id{};
Observer(int i) : id{ i }
{
std::cout << "Construct " << id << '\n';
}
Observer(const Observer& other) : id{ other.id }
{
std::cout << "Copy " << id << '\n';
}
~Observer()
{
std::cout << "Destruct " << id << '\n';
}
};
Observer makeObserver()
{
return Observer{42};
}
int main()
{
std::cout << "Before\n";
Observer obj{ makeObserver() };
std::cout << "After\n";
return 0;
}
Try compiling with different optimization levels to see elision in action.
Trust the compiler and don't try to outsmart copy elision. Return by value for local objects as the compiler optimizes this. Use direct initialization (Type obj{ value }) instead of copy initialization. Don't use std::move on local returns as it prevents RVO. Provide move constructors for large types as they're used when elision can't happen. Test with optimizations off to ensure your copy constructors are correct.
Summary
Copy elision: A compiler optimization that eliminates unnecessary copy and move operations, constructing objects directly in their final destination instead of creating temporaries.
Return Value Optimization (RVO): Eliminates copies when returning temporary objects from functions by constructing the return value directly in the caller's memory space.
Named Return Value Optimization (NRVO): Optimizes returns of named local variables, though this optimization is optional and compiler-dependent unlike guaranteed RVO for temporaries.
Guaranteed elision (C++17+): Certain forms of copy elision are guaranteed by the C++17 standard, particularly when initializing from temporaries or returning temporary objects.
Direct vs. copy initialization: Direct initialization (Type obj{value}) is clearer about intent, while copy initialization (Type obj = value) may invoke copy elision but is less explicit.
When elision doesn't happen: Multiple return paths or returning different objects prevents NRVO. In these cases, move semantics provide a fallback for efficient returns.
Don't use std::move on returns: Applying std::move to local return values prevents RVO and makes code less efficient, not more. Let the compiler handle optimization.
Copy elision is one of C++'s most important optimizations. Understanding when it applies lets you write natural, readable code that returns objects by value without performance concerns. The compiler handles the optimization automatically, so focus on clear code structure rather than manual optimization attempts.
Optimizing Object Construction - Quiz
Test your understanding of the lesson.
Practice Exercises
Copy Elision
Observe how the compiler can eliminate unnecessary copies through copy elision.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!