在现代 C++ 开发中,智能指针已经成为管理动态内存的标准手段。标准库提供了 std::unique_ptr、std::shared_ptr 和 std::weak_ptr,但在某些场景下我们可能需要一个具有特殊行为或内部逻辑的自定义智能指针。本文将演示如何从零开始实现一个简易的 SmartPtr,并介绍其常见的改进方向。
1. 基本需求
- 自动析构:对象不再使用时自动释放资源。
- 拷贝/移动语义:支持拷贝构造、移动构造、拷贝赋值、移动赋值。
- 访问操作:支持
operator*()、operator->()、get()。 - 安全性:空指针检查、避免悬空指针。
2. 设计思路
- 使用模板实现可兼容任意类型。
- 内部维护一个原始指针和引用计数(简化版本使用 `std::atomic ` 作为计数器)。
- 为了演示的简洁性,暂时不考虑自定义删除器;若需要可在构造函数中接收
std::function<void(T*)>。
3. 代码实现
#include <iostream>
#include <atomic>
#include <cassert>
template<typename T>
class SmartPtr {
public:
// 构造
explicit SmartPtr(T* ptr = nullptr)
: ptr_(ptr), count_(ptr ? new std::atomic <size_t>(1) : nullptr) {}
// 析构
~SmartPtr() { release(); }
// 拷贝构造
SmartPtr(const SmartPtr& other) noexcept
: ptr_(other.ptr_), count_(other.count_) {
increment();
}
// 移动构造
SmartPtr(SmartPtr&& other) noexcept
: ptr_(other.ptr_), count_(other.count_) {
other.ptr_ = nullptr;
other.count_ = nullptr;
}
// 拷贝赋值
SmartPtr& operator=(const SmartPtr& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
count_ = other.count_;
increment();
}
return *this;
}
// 移动赋值
SmartPtr& operator=(SmartPtr&& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
count_ = other.count_;
other.ptr_ = nullptr;
other.count_ = nullptr;
}
return *this;
}
// 访问操作符
T& operator*() const { assert(ptr_); return *ptr_; }
T* operator->() const { assert(ptr_); return ptr_; }
T* get() const noexcept { return ptr_; }
// 检查是否为空
explicit operator bool() const noexcept { return ptr_ != nullptr; }
// 获取引用计数(仅用于调试)
size_t use_count() const noexcept { return count_ ? *count_ : 0; }
private:
T* ptr_;
std::atomic <size_t>* count_;
void increment() noexcept {
if (count_) ++(*count_);
}
void release() noexcept {
if (count_) {
if (--(*count_) == 0) {
delete ptr_;
delete count_;
}
}
}
};
关键点说明
- 引用计数:使用
std::atomic保证多线程安全。每次拷贝构造或拷贝赋值都会递增计数,析构或移除时递减。 - 移动语义:移动构造/赋值后,原对象指针置空,避免双重释放。
- 空指针安全:访问运算符前使用
assert确认非空;在实际生产代码中可抛出异常或返回默认值。
4. 使用示例
int main() {
SmartPtr <int> p1(new int(42));
std::cout << "p1 use_count: " << p1.use_count() << std::endl; // 1
{
SmartPtr <int> p2 = p1; // 拷贝
std::cout << "p1 use_count: " << p1.use_count() << std::endl; // 2
std::cout << "p2 value: " << *p2 << std::endl; // 42
} // p2 离开作用域,计数递减
std::cout << "p1 use_count after p2 destroyed: " << p1.use_count() << std::endl; // 1
SmartPtr <int> p3 = std::move(p1); // 移动
std::cout << "p3 value: " << *p3 << std::endl;
std::cout << "p1 bool: " << static_cast<bool>(p1) << std::endl; // false
}
5. 可扩展功能
| 功能 | 说明 |
|---|---|
| 自定义删除器 | 在构造函数中接收 std::function<void(T*)>,在 release() 时调用。 |
| 线程安全的计数器 | 以上实现已使用 std::atomic;若性能更高需求可考虑细粒度锁或读写锁。 |
| 弱引用 | 实现 WeakPtr,仅维护计数指针,不拥有资源,适用于 std::weak_ptr 的实现思路。 |
| 多重继承或数组支持 | 通过模板特化或重载 operator[] 处理数组。 |
| 异常安全 | 通过 RAII 结合 try/catch 保证异常不泄漏资源。 |
6. 与标准库比较
- 功能:
SmartPtr提供了与std::shared_ptr基本相同的拷贝/移动语义。缺点是没有自定义删除器和线程安全的细粒度控制。 - 性能:标准库实现经过多年优化,内部使用引用计数与对象分配结合的技巧;自定义实现往往在性能和安全性上略逊一筹。
- 可维护性:使用标准库可避免错误、提升代码可读性;自定义实现可用于学习或满足特殊需求,但需要额外维护。
7. 结语
通过上述代码,你可以快速得到一个可用于教学或小型项目的智能指针。若你需要更强大的功能,建议直接使用 STL 的 std::shared_ptr、std::unique_ptr,或者在此基础上进行扩展。理解底层实现有助于你在遇到性能或特殊需求时做出更合理的选择。祝你编码愉快!