C++ RAII & Smart Pointers: Ownership and Automatic Cleanup
이 글의 핵심
Practical guide to RAII and smart pointers—how they work together for safe resource and memory management.
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
#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 ownershared_ptr: shared ownership, multiple ownersweak_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