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
deletecalled
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
Related posts (internal links)
- 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
Related posts
- Buffer overflow
- Cache optimization
- Custom allocator
- Dangling reference
- Flyweight pattern