C++ Object Lifetime: Storage Duration, RAII, and Dangling
이 글의 핵심
C++ lifetime and storage duration: automatic, static, dynamic, thread_local; destruction order; temporaries; smart pointers and RAII.
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
Here is the func implementation:
// 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)
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
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Dangling Reference | ‘댕글링 레퍼런스’ 가이드
- C++ Object Pool | ‘객체 풀’ 가이드
- C++ Temporary Objects | ‘임시 객체’ 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, Lifetime, Storage Duration, RAII, Memory 등으로 검색하시면 이 글이 도움이 됩니다.