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.
// ❌ 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.
// ❌ 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:
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 |
// 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
The following example demonstrates the concept in cpp:
#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
// 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
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
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
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
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
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
// ❌ 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
// ❌ 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
// ❌ 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
// FILE* management
auto deleter = [](FILE* f) {
if (f) fclose(f);
};
std::unique_ptr<FILE, decltype(deleter)> file(
fopen("file.txt", "r"), deleter
);
Performance comparison
// 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
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
#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
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.
// 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)
// 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[]>.
// ❌ 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.
// ❌ 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
// 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.
// ❌ 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.
// 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.
Keywords covered in this article (related search terms)
This article will be helpful if you search for C++, RAII, smart-pointers, unique_ptr, shared_ptr, etc.
Related articles
- C++ shared_ptr vs unique_ptr |
- 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
- C++ RAII Complete Guide |
- C++ smart pointer |unique_ptr/shared_ptr
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ weak_ptr | ‘약한 포인터’ 가이드
- C++ 스마트 포인터 | unique_ptr/shared_ptr ‘메모리 안전’ 가이드
- C++ 스마트 포인터 | 3일 동안 찾지 못한 순환 참조 버그 해결법
이 글에서 다루는 키워드 (관련 검색어)
C++, RAII, smart pointers, unique_ptr, shared_ptr, weak_ptr, memory management 등으로 검색하시면 이 글이 도움이 됩니다.