[2026] C++ RAII & Smart Pointers: Ownership and Automatic Cleanup
이 글의 핵심
RAII in C++: acquire resources in constructors, release in destructors—plus unique_ptr, shared_ptr, weak_ptr, patterns, and common pitfalls with examples.
What are RAII and smart pointers?
RAII (Resource Acquisition Is Initialization) means binding resources to object lifetime: acquire in construction, release in destruction. Smart pointers are the standard way to express heap ownership with RAII. 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ Manual management
int* ptr = new int(10);
// ....leak when exception occurs
delete ptr;
// ✅ Smart pointer
auto ptr = std::make_unique<int>(10);
// automatic destruction
Why do you need it?:
- Auto-Release: Automatic cleanup in destructor.
- Exception safety: Safe even when exceptions occur
- Memory leak prevention: Prevent missing deletes
- Clear ownership: It is clear who owns the resource. 다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Manual management: Leakage on exception
// 실행 예제
void func() {
int* ptr = new int(10);
if (error) {
throw std::runtime_error("error");// Leak!
}
delete ptr;
}
// ✅ RAII: exception safe
void func() {
auto ptr = std::make_unique<int>(10);
if (error) {
throw std::runtime_error("error");// automatic release
}
}
RAII operating principle: 아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
flowchart LR
A[Construct object] --> B[Acquire resource]
B --> C[Use]
C --> D[Exception?]
D -->|Yes| E[Stack unwinds]
D -->|No| F[Normal exit]
E --> G[Destructor runs]
F --> G
G --> H[Release resource]
Smart pointer types:
| Type | Ownership | Copy | Go | Usage Scenarios |
|---|---|---|---|---|
unique_ptr | Exclusive | ❌ | ✅ | Single Owner |
shared_ptr | Share | ✅ | ✅ | Multiple Owners |
weak_ptr | None | ✅ | ✅ | Avoid circular references |
| 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다. |
// unique_ptr: exclusive ownership
std::unique_ptr<int> u = std::make_unique<int>(10);
// auto u2 = u;// Error: Copy not possible
auto u2 = std::move(u);// OK: Move
// shared_ptr: shared ownership
std::shared_ptr<int> s = std::make_shared<int>(10);
auto s2 = s;// OK: copy (increment reference count)
// weak_ptr: observation only
std::weak_ptr<int> w = s;
if (auto ptr = w.lock()) { // lock when used
std::cout << *ptr << '\n';
}
unique_ptr
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.
#include <memory>
// exclusive ownership
std::unique_ptr<int> ptr = std::make_unique<int>(10);
// Only movement possible
auto ptr2 = std::move(ptr); // ptr is nullptr
shared_ptr
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// shared ownership
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::shared_ptr<int> ptr2 = ptr1;// increase reference count
// Resources are released when the last shared_ptr is destroyed
Practical example
Example 1: unique_ptr default
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Resource {
public:
Resource() { std::cout << "ctor" << std::endl; }
~Resource() { std::cout << "dtor" << std::endl; }
void use() { std::cout << "use" << std::endl; }
};
void func() {
auto ptr = std::make_unique<Resource>();
ptr->use();
// Automatically destroyed when the function ends
}
int main() {
func();
// "Create" -> "Use" -> "Destroy"
}
Example 2: shared_ptr reference count
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
void func() {
auto ptr1 = std::make_shared<int>(10);
std::cout << "Count: " << ptr1.use_count() << std::endl;// 1
{
auto ptr2 = ptr1;
std::cout << "Count: " << ptr1.use_count() << std::endl;// 2
}
std::cout << "Count: " << ptr1.use_count() << std::endl;// 1
}
Example 3: Container
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::vector<std::unique_ptr<Widget>> widgets;
widgets.push_back(std::make_unique<Widget>(1));
widgets.push_back(std::make_unique<Widget>(2));
// When the vector is destroyed, all widgets are automatically destroyed.
Example 4: Factory pattern
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Circle" << std::endl;
}
};
std::unique_ptr<Shape> createShape(const std::string& type) {
if (type == "circle") {
return std::make_unique<Circle>();
}
return nullptr;
}
weak_ptr
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
auto shared = std::make_shared<int>(10);
std::weak_ptr<int> weak = shared;
// lock when using
if (auto ptr = weak.lock()) {
std::cout << *ptr << std::endl;
}
Frequently occurring problems
Problem 1: Circular references
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Circular reference
class Node {
public:
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev;// circular reference
};
// ✅ Use weak_ptr
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev;// prevent circulation
};
Problem 2: this pointer
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ this to shared_ptr
class Bad {
public:
std::shared_ptr<Bad> getPtr() {
return std::shared_ptr<Bad>(this); // unsafe!
}
};
// ✅ enable_shared_from_this
class Good : public std::enable_shared_from_this<Good> {
public:
std::shared_ptr<Good> getPtr() {
return shared_from_this();
}
};
Problem 3: Arrays
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ Array deletion problem
std::unique_ptr<int> ptr(new int[10]); // calls delete (wrong for array)
// ✅ Array specialization
std::unique_ptr<int[]> ptr(new int[10]); // calls delete[]
// ✅ make_unique (C++14)
auto ptr = std::make_unique<int[]>(10);
Issue 4: Custom deleter
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// FILE* management
auto deleter = [](FILE* f) {
if (f) fclose(f);
};
std::unique_ptr<FILE, decltype(deleter)> file(
fopen("file.txt", "r"), deleter
);
Performance comparison
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// unique_ptr: pointer-sized
sizeof(std::unique_ptr<int>); // typically 8 bytes
// shared_ptr: object ptr + control block ptr
sizeof(std::shared_ptr<int>); // typically 16 bytes
Production patterns
Pattern 1: Factory function
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Connection {
public:
Connection(const std::string& host) : host_(host) {
std::cout << "connect: " << host_ << '\n';
}
~Connection() {
std::cout << "disconnect: " << host_ << '\n';
}
void query(const std::string& sql) {
std::cout << "query: " << sql << '\n';
}
private:
std::string host_;
};
std::unique_ptr<Connection> createConnection(const std::string& host) {
return std::make_unique<Connection>(host);
}
// use
auto conn = createConnection("localhost");
conn->query("SELECT * FROM users");
// Automatic connection termination
Pattern 2: Cache System
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <map>
#include <memory>
#include <string>
class Cache {
std::map<std::string, std::weak_ptr<Resource>> cache_;
public:
std::shared_ptr<Resource> get(const std::string& key) {
// check cache
if (auto it = cache_.find(key); it != cache_.end()) {
if (auto ptr = it->second.lock()) {
return ptr;// cache hit
}
}
// Cache miss: create new
auto resource = std::make_shared<Resource>(key);
cache_[key] = resource;
return resource;
}
};
Pattern 3: Passing ownership
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class TaskQueue {
std::vector<std::unique_ptr<Task>> tasks_;
public:
void addTask(std::unique_ptr<Task> task) {
tasks_.push_back(std::move(task));
}
std::unique_ptr<Task> getNextTask() {
if (tasks_.empty()) {
return nullptr;
}
auto task = std::move(tasks_.back());
tasks_.pop_back();
return task;
}
};
// use
TaskQueue queue;
queue.addTask(std::make_unique<Task>("Task1"));
auto task = queue.getNextTask();// transfer ownership
task->execute();
FAQ
Q1: When should I use it?
A:
- unique_ptr: exclusive ownership, single owner
- shared_ptr: shared ownership, multiple owners
- weak_ptr: Avoid circular references, observe only. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// unique_ptr: exclusive
std::unique_ptr<Widget> widget = std::make_unique<Widget>();
// shared_ptr: shared
std::shared_ptr<Widget> shared = std::make_shared<Widget>();
auto shared2 = shared;// copy
// weak_ptr: observation
std::weak_ptr<Widget> weak = shared;
Q2: What is the performance?
A:
- unique_ptr: Same as raw pointer (no overhead)
- shared_ptr: Reference count overhead (atomic operation) 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// unique_ptr: 8 bytes
sizeof(std::unique_ptr<int>); // 8
// shared_ptr: 16 bytes (pointer + control block)
sizeof(std::shared_ptr<int>); // 16
Q3: How do I use arrays?
A: Use unique_ptr<T[]>. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ Array deletion problem
std::unique_ptr<int> ptr(new int[10]);// call delete (wrong)
// ✅ Array specialization
std::unique_ptr<int[]> ptr(new int[10]); // calls delete[]
// ✅ make_unique (C++14)
auto ptr = std::make_unique<int[]>(10);
Q4: How do I resolve circular references?
A: Solved with weak_ptr. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Circular reference
class Node {
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev;// circular reference
};
// ✅ weak_ptr
class Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev;// prevent circulation
};
Q5: When to use raw pointer?
A:
- No Ownership: Observation only
- Function parameters: temporary reference
- Performance Critical: Hot Pass 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// raw pointer: no ownership
void process(Widget* widget) {
widget->use();// observation only
}
// unique_ptr: Ownership
std::unique_ptr<Widget> owner = std::make_unique<Widget>();
process(owner.get()); // pass raw pointer (non-owning)
Q6: make_unique vs new?
A: make_unique is recommended.
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ new: exception unsafe
func(std::unique_ptr<Widget>(new Widget()), compute());
// Possible leak when exception occurs in compute()
// ✅ make_unique: exception safe
func(std::make_unique<Widget>(), compute());
Q7: What about custom deleters?
A: Use lambda or function object. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// FILE* management
auto deleter = [](FILE* f) {
if (f) fclose(f);
};
std::unique_ptr<FILE, decltype(deleter)> file(
fopen("file.txt", "r"), deleter
);
Q8: What are smart pointer learning resources?
A:
- “Effective Modern C++” by Scott Meyers (Item 18-22)
- “C++ Primer” by Stanley Lippman
- cppreference.com - Smart pointers See also: unique_ptr, shared_ptr, weak_ptr. One-line summary: RAII and smart pointers are core C++ techniques that prevent memory leaks with automatic resource management.
Good article to read together (internal link)
Here’s another article related to this topic.
- C++ smart pointer |unique_ptr/shared_ptr “Memory Safety” Guide
- C++ smart pointer |A solution to a circular reference bug that I couldn’t find in 3 days
- Complete guide to C++ smart pointer basics |unique_ptr·shared_ptr
Practical tips
These are tips that can be applied right away in practice.
Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case
Performance Tips
- Don’t optimize without profiling
- Set measurable indicators first
Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team’s coding conventions
Practical checklist
This is what you need to check when applying this concept in practice.
Before writing code
- Is this technique the best way to solve the current problem?
- Can team members understand and maintain this code?
- Does it meet the performance requirements?
Writing code
- Have you resolved all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
When reviewing code
- Is the intention of the code clear?
- Are there enough test cases?
- Is it documented? Use this checklist to reduce mistakes and improve code quality.