在现代 C++ 开发中,std::unique_ptr、std::shared_ptr 和 std::weak_ptr 已经为我们提供了强大的资源管理能力。但在某些特殊场景下,标准库的实现并不完全满足需求,例如:
- 需要对资源释放过程做更细粒度的控制;
- 需要在指针生命周期中插入自定义的日志、计数或安全检查;
- 需要兼容旧有代码或第三方库的接口。
下面以一个“自定义共享指针(MySharedPtr)”为例,展示如何从零实现一个可替代 std::shared_ptr 的简易版本,并说明其核心实现思路。
1. 设计目标
- 引用计数:采用原子计数,支持多线程安全。
- 自定义释放策略:允许用户传入自定义
Deleter。 - 内联存储:在小对象场景下使用“对象分配器”把指针和计数放在同一次内存分配中,减少分配次数。
- 兼容性:提供
operator*、operator->、get()、use_count()等接口。
2. 核心实现细节
template <typename T, typename Deleter = std::default_delete<T>>
class MySharedPtr {
public:
// 构造
explicit MySharedPtr(T* ptr = nullptr, Deleter del = Deleter())
: control_(nullptr), ptr_(ptr), deleter_(del) {
if (ptr_) {
control_ = new ControlBlock(ptr_, deleter_);
}
}
// 拷贝构造
MySharedPtr(const MySharedPtr& other)
: control_(other.control_), ptr_(other.ptr_), deleter_(other.deleter_) {
if (control_) control_->add_ref();
}
// 移动构造
MySharedPtr(MySharedPtr&& other) noexcept
: control_(other.control_), ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
other.control_ = nullptr;
other.ptr_ = nullptr;
}
// 析构
~MySharedPtr() {
release();
}
// 赋值
MySharedPtr& operator=(MySharedPtr other) noexcept {
swap(other);
return *this;
}
// 交换
void swap(MySharedPtr& other) noexcept {
std::swap(control_, other.control_);
std::swap(ptr_, other.ptr_);
std::swap(deleter_, other.deleter_);
}
// 访问
T* get() const noexcept { return ptr_; }
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
size_t use_count() const noexcept { return control_ ? control_->use_count() : 0; }
explicit operator bool() const noexcept { return ptr_ != nullptr; }
private:
struct ControlBlock {
std::atomic <size_t> count;
Deleter del;
T* ptr;
ControlBlock(T* p, Deleter d) : count(1), del(d), ptr(p) {}
void add_ref() { count.fetch_add(1, std::memory_order_relaxed); }
void release() {
if (count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
del(ptr);
delete this;
}
}
};
void release() {
if (control_) {
control_->release();
control_ = nullptr;
ptr_ = nullptr;
}
}
ControlBlock* control_;
T* ptr_;
Deleter deleter_;
};
关键点说明
-
控制块(
ControlBlock)- 存储引用计数、删除器和原始指针。
- 采用
std::atomic实现线程安全。 release()在计数归零时调用删除器并释放控制块自身。
-
自定义删除器
- 模板参数
Deleter默认是 `std::default_delete `。 - 通过
MySharedPtr(ptr, deleter)可以传入任何可调用对象,例如 lambda、函数指针或自定义类。
- 模板参数
-
内联分配
- 若需要在控制块与对象共存,改用单次
operator new分配一块内存,将对象和计数包装在同一块中。 - 这里为演示简化,直接分配两块内存。
- 若需要在控制块与对象共存,改用单次
3. 使用示例
struct MyStruct {
int a;
~MyStruct() { std::cout << "MyStruct destroyed\n"; }
};
int main() {
// 使用默认删除器
MySharedPtr <MyStruct> sp1(new MyStruct{42});
std::cout << "use_count: " << sp1.use_count() << '\n'; // 1
{
MySharedPtr <MyStruct> sp2 = sp1; // 拷贝
std::cout << "use_count: " << sp1.use_count() << '\n'; // 2
} // sp2 离开作用域
std::cout << "use_count: " << sp1.use_count() << '\n'; // 1
// 使用自定义删除器
auto deleter = [](MyStruct* p){
std::cout << "Custom delete\n";
delete p;
};
MySharedPtr <MyStruct> sp3(new MyStruct{99}, deleter);
// sp3 离开时会调用 deleter
}
运行结果示例:
use_count: 1
use_count: 2
use_count: 1
MyStruct destroyed
Custom delete
4. 性能与扩展
- 性能:相较于
std::shared_ptr,上述实现缺少内联计数、分配优化等,适合作为教学示例。 - 扩展:
- 加入
weak_ptr版本:MyWeakPtr,管理弱引用。 - 对象池化:将
ControlBlock放入自定义池中。 - 支持数组:专门的
MyArrayPtr,兼容delete[]。
- 加入
5. 小结
通过上述实现,我们可以看到自定义智能指针的核心机制:引用计数、删除器和资源解放。
在实际项目中,如果现有智能指针无法满足特殊需求,完全可以基于此思路自行扩展;否则建议直接使用 STL 提供的 std::shared_ptr、std::unique_ptr 等标准实现,以获得更完善的错误检查、异常安全和性能优化。