C++ Object Lifetime: Storage Duration, RAII, and Dangling

C++ Object Lifetime: Storage Duration, RAII, and Dangling

이 글의 핵심

Practical guide to object lifetime in C++: when objects begin and end, storage classes, and how RAII ties destruction to scope.

What is lifetime?

The period from when an object’s lifetime begins (after construction completes) until it ends (destruction).

void func() {
    int x = 10;  // x begins
    // x is usable here
}  // x ends

Storage duration

// 1. Automatic
void func() {
    int x = 10;  // destroyed at end of block
}

// 2. Static
int global = 10;  // program end

void func() {
    static int count = 0;  // program end
}

// 3. Dynamic
void func() {
    int* ptr = new int(10);  // until delete
    delete ptr;
}

// 4. Thread
thread_local int x = 10;  // thread end

Practical examples

Example 1: Automatic storage

#include <iostream>

class Widget {
public:
    Widget(int id) : id(id) {
        std::cout << "Widget " << id << " ctor" << std::endl;
    }
    
    ~Widget() {
        std::cout << "Widget " << id << " dtor" << std::endl;
    }
    
private:
    int id;
};

void func() {
    Widget w1(1);
    
    if (true) {
        Widget w2(2);
        // w2 lifetime: this block
    }  // w2 destroyed
    
    Widget w3(3);
}  // w3, w1 destroyed (reverse order)

int main() {
    func();
}

Example 2: Static storage

#include <iostream>

class Logger {
public:
    Logger() {
        std::cout << "Logger init" << std::endl;
    }
    
    ~Logger() {
        std::cout << "Logger shutdown" << std::endl;
    }
    
    void log(const std::string& msg) {
        std::cout << "[LOG] " << msg << std::endl;
    }
};

Logger globalLogger;

void func() {
    static Logger localLogger;
    localLogger.log("func");
}

int main() {
    globalLogger.log("start");
    func();
    func();
    globalLogger.log("end");
}

Example 3: Dynamic storage

#include <memory>

class Resource {
public:
    Resource(int size) : size(size) {
        data = new int[size];
        std::cout << "Resource alloc " << size << std::endl;
    }
    
    ~Resource() {
        delete[] data;
        std::cout << "Resource free " << size << std::endl;
    }
    
private:
    int* data;
    int size;
};

void manualManagement() {
    Resource* res = new Resource(100);
    delete res;
}

void smartPointer() {
    auto res = std::make_unique<Resource>(100);
}

int main() {
    manualManagement();
    smartPointer();
}

Example 4: Lifetime extension

#include <iostream>

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

Temp createTemp(int val) {
    return Temp(val);
}

int main() {
    const Temp& ref = createTemp(10);
    std::cout << "value: " << ref.getValue() << std::endl;
    // destroyed when ref goes out of scope
}

Construction / destruction order

#include <iostream>

class A {
public:
    A(int id) : id(id) {
        std::cout << "A" << id << " ctor" << std::endl;
    }
    
    ~A() {
        std::cout << "A" << id << " dtor" << std::endl;
    }
    
private:
    int id;
};

A global1(1);  // global first

int main() {
    A local1(2);
    static A static1(3);
    A local2(4);
    
    // Destroy order: local2, local1, static1, global1
}

Common pitfalls

Pitfall 1: Dangling reference

const std::string& func() {
    std::string s = "Hello";
    return s;
}

int main() {
    const std::string& ref = func();  // UB
}

// Fix: return by value
std::string func() {
    std::string s = "Hello";
    return s;
}

Pitfall 2: Dangling pointer

int* func() {
    int x = 10;
    return &x;
}

int main() {
    int* ptr = func();  // UB
}

// Fix: dynamic allocation or return by value

Pitfall 3: Static initialization order

// file1.cpp
int globalA = 10;

// file2.cpp
extern int globalA;
int globalB = globalA * 2;  // order across TUs unspecified

// Safer: function-local static
int& getGlobalA() {
    static int globalA = 10;
    return globalA;
}

Pitfall 4: Temporary lifetime

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

const char* ptr = getName().c_str();  // UB after full expression

const std::string& name = getName();
const char* ptr = name.c_str();  // OK until name dies

Smart pointers and lifetime

#include <memory>

class Resource {
public:
    Resource() {
        std::cout << "Resource ctor" << std::endl;
    }
    
    ~Resource() {
        std::cout << "Resource dtor" << std::endl;
    }
};

void uniquePtr() {
    auto res = std::make_unique<Resource>();
}

void sharedPtr() {
    auto res1 = std::make_shared<Resource>();
    {
        auto res2 = res1;
    }
}

int main() {
    uniquePtr();
    sharedPtr();
}

RAII

#include <fstream>
#include <mutex>

void writeFile(const std::string& filename) {
    std::ofstream file(filename);
    file << "Hello";
}

std::mutex mtx;

void criticalSection() {
    std::lock_guard<std::mutex> lock(mtx);
    // critical section
}

FAQ

Q1: When does lifetime start?

A: After construction completes and initialization finishes.

Q2: When does it end?

A:

  • Destructor runs
  • Scope ends
  • delete called

Q3: Storage kinds?

A:

  • Automatic (local)
  • Static (global / static)
  • Dynamic (new/delete)
  • thread_local

Q4: Avoid dangling?

A:

  • Smart pointers
  • Return by value
  • RAII

Q5: Temporary extension?

A: const T& to a temporary extends for the reference’s scope in many cases.

Q6: Learning resources?

A:

  • Effective C++
  • C++ Primer
  • cppreference.com

  • Dangling reference
  • Use after free
  • Memory leak

Practical tips

Debugging

  • Warnings first

Performance

  • Measure before tuning

Code review

  • Conventions

Practical checklist

Before coding

  • Right approach?
  • Maintainable?
  • Performance?

While coding

  • Warnings?
  • Edge cases?
  • Errors?

At review

  • Intent?
  • Tests?
  • Docs?

Keywords

C++, lifetime, storage duration, RAII, memory


  • Buffer overflow
  • Cache optimization
  • Custom allocator
  • Dangling reference
  • Flyweight pattern