In C++, manual resource management (<span>new/delete</span>) is prone to errors, leading to:
•Memory leaks•Double free•Exception safety issues
To address these problems, C++11 introduced smart pointers:
•<span>std::unique_ptr</span>: Exclusive ownership•<span>std::shared_ptr</span>: Shared ownership, managed through reference counting
We will analyze from three dimensions: principles, performance, and application scenarios.
1. Technical Background and Design Intent
1unique_ptr•
Goal: Exclusive resource ownership, non-copyable, can only be moved (move)
•
Usage scenario: Unique ownership of resources, RAII style automatic release
•
Core issues:
•Avoid memory leaks•Support move semantics1shared_ptr•
Goal: Multiple parties share resources, reference counting controls lifecycle
•
Usage scenario: Multi-threaded shared objects, cache management
•
Core issues:
•Automatic lifecycle management•Handle reference counting in multi-threaded environments (atomic)•Avoid circular reference issues
2. Implementation Principles of unique_ptr
1. Internal Structure
template<typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
T* ptr; // Raw pointer
Deleter deleter; // Deleter
public:
explicit unique_ptr(T* p = nullptr) noexcept : ptr(p) {}
~unique_ptr() { if(ptr) deleter(ptr); }
// Non-copyable
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
// Movable
unique_ptr(unique_ptr&& u) noexcept : ptr(u.ptr) { u.ptr = nullptr; }
unique_ptr& operator=(unique_ptr&& u) noexcept {
if(this != &u) {
reset(u.ptr);
u.ptr = nullptr;
}
return *this;
}
T* get() const noexcept { return ptr; }
T& operator*() const noexcept { return *ptr; }
T* operator->() const noexcept { return ptr; }
void reset(T* p = nullptr) {
if(ptr) deleter(ptr);
ptr = p;
}
};
2. Core Principles
•Exclusive ownership: The unique pointer owns the resource, copying is disabled•Move semantics: Resource transfer is achieved through moving•RAII: Automatic release at the end of the lifecycle
3. Performance Characteristics
•Small memory footprint (only stores raw pointer + deleter)•No additional reference counting overhead•Low compile-time overhead•Exception safe, avoids leaks
3. Implementation Principles of shared_ptr
1. Internal Structure
The core of shared_ptr lies in reference counting and control block.
shared_ptr<T>
├── T* ptr // Actual object pointer
└── ControlBlock* cb // Reference count and deleter
Example of control block structure:
struct ControlBlock {
std::atomic<size_t> use_count; // Strong reference count
std::atomic<size_t> weak_count; // Weak reference count
Deleter deleter; // Deleter
T* ptr; // Pointer to object
};
2. Core Logic
•Constructing shared_ptr: Initializes <span>use_count = 1</span>•Copying shared_ptr: <span>use_count++</span> (atomic operation)•Destructing shared_ptr: <span>use_count--</span>, if it is 0, call deleter to destroy the object•weak_ptr: Does not increase <span>use_count</span>, only increases <span>weak_count</span>, used to resolve circular references
3. Key Implementation Details
•
Thread safety: <span>std::atomic</span> ensures reference counting is correct in multi-threading
•
Control block separation:
•Object pointer and control block are separated, allowing support for <span>make_shared</span> inline allocation (reducing memory fragmentation)•
Circular reference issues:
•Two shared_ptr referencing each other will cause the reference count not to be 0•Requires weak_ptr to resolve
4. Performance Analysis
| Feature | unique_ptr | shared_ptr |
|---|---|---|
| Memory usage | Small (pointer + deleter) | Additional overhead for control block, atomic counting |
| Copy/Move overhead | Copy is prohibited, move overhead is small | Copy requires atomic operation, move overhead is small |
| Multi-thread safety | Depends on external protection | Atomic operations on reference counting ensure safety |
| Circular reference risk | None | Exists, requires weak_ptr |
| Applicable scenarios | Exclusive resources, RAII | Multi-party shared object management, caching, asynchronous tasks |
5. Applications in Distributed Systems
1unique_ptr•In Ceph’s object cache (ObjectCacher), each cache entry exclusively owns a memory block•TiDB Executor manages local temporary objects1shared_ptr•Ceph multi-threaded I/O callback objects are shared•TiDB RPC/asynchronous tasks share context
The core idea: Use unique_ptr whenever possible to avoid atomic overhead; use shared_ptr only when multiple parties need to share, and combine with weak_ptr to avoid circular references.
6. Conclusion
•
unique_ptr
•Exclusive resource ownership, low overhead, RAII automatic release•Ownership transfer achieved through move semantics•
shared_ptr
•Shared resources, managed through reference counting + control blocklifecycle•Supports multi-threading, but with performance overhead•Circular references need to be resolved with weak_ptr•
In Distributed Systems
•Use unique_ptr for hotspot objects•Use shared_ptr for asynchronous callbacks and shared context
In summary:“Use unique_ptr for unique ownership, use shared_ptr when sharing is necessary, and controlling reference counting is controlling your performance.”