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.

Key Concept
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.

Best Practice
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.