C++ Temporary Objects: Lifetime, const&, RVO, and Pitfalls

C++ Temporary Objects: Lifetime, const&, RVO, and Pitfalls

이 글의 핵심

Guide to unnamed temporaries: when they die, lifetime extension, subobject exceptions, and avoiding dangling references.

What are temporary objects?

Temporary objects are unnamed objects created while evaluating an expression. Usually they are destroyed at the end of the full expression, unless bound to const lvalue reference or certain rvalue references (lifetime extension rules apply).

std::string s = std::string("Hello");  // temporary then named

int x = 1 + 2;  // 3 as a temporary value

Why care:

  • Performance: extra temporaries cost time
  • Safety: dangling references to dead temporaries
  • Optimization: ties to RVO/NRVO and moves

Temporary vs named object:

NamedTemporary
NameYesNo
LifetimeScopeUsually end of full expression
ExtensionPossible with const&
MovableYesYes
std::string name("Alice");  // lives until scope ends
std::string("Alice");       // destroyed at end of full expression

When temporaries appear

// Function return
std::string getName() {
    return "Alice";
}

// Conversion
void func(std::string s) {}
func("Hello");  // const char* -> std::string temporary

// Operators
std::string s = "Hello" + std::string(" World");

// Explicit prvalue
Widget(10);

Lifetime rules

class Temp {
public:
    Temp(int val) : value(val) {
        std::cout << "ctor " << value << std::endl;
    }
    
    ~Temp() {
        std::cout << "dtor " << value << std::endl;
    }
    
private:
    int value;
};

void func() {
    Temp(10);  // destroyed before next statement
    std::cout << "next line" << std::endl;
}
  1. Default: temporaries die at the end of the full expression.

  2. Lifetime extension: binding a temporary to const T& or certain rvalue refs can extend lifetime to the reference’s scope.

  3. Exception: a reference to a subobject of a temporary (e.g. getOuter().inner) does not extend the whole temporary’s lifetime in the same way—classic dangling hazard.

Practical examples

Lifetime extension

std::string getName() {
    return "Alice";
}

int main() {
    // Dangerous
    const char* ptr = getName().c_str();
    
    // Safer
    const std::string& name = getName();
    const char* ptr2 = name.c_str();
}

Function arguments

void process(const Widget& w) {
    std::cout << "process " << w.getValue() << std::endl;
}

int main() {
    process(Widget(10));  // temporary lives for the call
}

Return and RVO

std::vector<int> createVector(size_t size) {
    std::vector<int> result(size);
    for (size_t i = 0; i < size; i++) {
        result[i] = static_cast<int>(i);
    }
    return result;  // often no copy (RVO/NRVO)
}

Operator overloads returning temporaries

class Vector2 {
    float x, y;
public:
    Vector2(float x, float y) : x(x), y(y) {}
    
    Vector2 operator+(const Vector2& other) const {
        return Vector2(x + other.x, y + other.y);
    }
};

Optimizing temporaries

// RVO
std::string func1() {
    return std::string("Hello");
}

// NRVO
std::string func2() {
    std::string result = "Hello";
    return result;
}

// Move when RVO does not apply
std::string func3() {
    std::string result = "Hello";
    return result;
}

Common pitfalls

Dangling from c_str()

Bind the owning std::string first, then call c_str().

Non-const lvalue reference to temporary

Only const T& and T&& can bind to temporaries in the usual rules.

Subobject references

struct Inner { int value = 42; };
struct Outer { Inner inner; };
Outer getOuter() { return Outer{}; }

// Dangerous
const Inner& bad = getOuter().inner;

// Better
const Outer& outer = getOuter();
const Inner& ok = outer.inner;

Iterator into temporary container

Store the container, then take iterators from the stored object.

FAQ

Q1: When are temporaries created?

A: Returns, conversions, operators, explicit prvalues.

Q2: Lifetime?

A: Usually end of full expression; extended when bound per language rules.

Q3: Performance?

A: RVO/NRVO and moves remove most overhead—avoid redundant work in loops.

Q4: Avoid dangling?

A: Name the owner, use const std::string& extension carefully, avoid subobject refs to temporaries.

Q5: Optimization tips?

A: Prefer +=, reserve capacity, avoid redundant conversions, do not std::move returned locals blindly.

Q6: Can a non-const lvalue ref bind to a temporary?

A: No—use const& or rvalue references.


  • Dangling reference
  • Lifetime
  • Copy elision

Practical tips

Debugging

  • Warnings first

Performance

  • Profile

Code review

  • Conventions

Practical checklist

Before coding

  • Right approach?
  • Maintainable?
  • Performance?

While coding

  • Warnings?
  • Edge cases?
  • Errors?

At review

  • Intent?
  • Tests?
  • Docs?

Keywords

C++, temporary objects, lifetime, RVO, move semantics


  • C++ series
  • Adapter pattern
  • ADL
  • Aggregate initialization