在 C++ 现代化进程中,智能指针(如 std::shared_ptr、std::unique_ptr)已经成为管理资源的重要工具。若想在自己的项目中使用自定义智能指针,核心任务之一就是正确实现拷贝和移动语义。下面以一个简易的 RefCountPtr 为例,演示完整的实现思路和关键细节。
1. 设计目标
- 自动管理引用计数:多个
RefCountPtr对同一对象时,共享同一计数。 - 支持拷贝构造/赋值:拷贝后引用计数递增。
- 支持移动构造/赋值:移动后原对象置空,计数不变。
- 防止循环引用:通过弱引用
WeakRefCountPtr解决。
2. 基础实现
#include <atomic>
#include <iostream>
#include <utility>
template<typename T>
class RefCountPtr {
public:
explicit RefCountPtr(T* ptr = nullptr)
: ptr_(ptr), count_(ptr ? new std::atomic <size_t>(1) : nullptr) {}
// 拷贝构造
RefCountPtr(const RefCountPtr& other) noexcept
: ptr_(other.ptr_), count_(other.count_) {
inc();
}
// 移动构造
RefCountPtr(RefCountPtr&& other) noexcept
: ptr_(other.ptr_), count_(other.count_) {
other.ptr_ = nullptr;
other.count_ = nullptr;
}
// 拷贝赋值
RefCountPtr& operator=(const RefCountPtr& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
count_ = other.count_;
inc();
}
return *this;
}
// 移动赋值
RefCountPtr& operator=(RefCountPtr&& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
count_ = other.count_;
other.ptr_ = nullptr;
other.count_ = nullptr;
}
return *this;
}
~RefCountPtr() {
release();
}
T* operator->() const noexcept { return ptr_; }
T& operator*() const noexcept { return *ptr_; }
explicit operator bool() const noexcept { return ptr_; }
size_t use_count() const noexcept { return count_ ? *count_ : 0; }
private:
void inc() noexcept {
if (count_) ++(*count_);
}
void release() noexcept {
if (count_ && --(*count_) == 0) {
delete ptr_;
delete count_;
}
}
T* ptr_;
std::atomic <size_t>* count_;
};
关键点说明
- 原子计数:使用 `std::atomic ` 以保证多线程安全。
release():递减计数,若为零则销毁对象与计数器。- 移动操作:移动后将源对象的指针和计数器置空,避免重复释放。
3. 弱引用实现
循环引用时,RefCountPtr 无法自行断开。通过弱引用来解决:
template<typename T>
class WeakRefCountPtr {
public:
explicit WeakRefCountPtr(const RefCountPtr <T>& src)
: ptr_(src.ptr_), count_(src.count_) {}
WeakRefCountPtr(const WeakRefCountPtr& other)
: ptr_(other.ptr_), count_(other.count_) {}
WeakRefCountPtr& operator=(const WeakRefCountPtr& other) {
ptr_ = other.ptr_;
count_ = other.count_;
return *this;
}
// 尝试升级为强引用
RefCountPtr <T> lock() const {
if (count_ && *count_ > 0) {
return RefCountPtr <T>(ptr_, count_);
}
return RefCountPtr <T>(); // 空指针
}
private:
T* ptr_;
std::atomic <size_t>* count_;
};
4. 测试案例
struct Node {
int value;
Node(int v) : value(v) { std::cout << "Node(" << value << ") constructed\n"; }
~Node() { std::cout << "Node(" << value << ") destroyed\n"; }
};
int main() {
RefCountPtr <Node> p1(new Node(10));
{
RefCountPtr <Node> p2 = p1; // 拷贝
std::cout << "use_count: " << p1.use_count() << "\n";
} // p2 结束,计数减一
std::cout << "use_count after block: " << p1.use_count() << "\n";
RefCountPtr <Node> p3 = std::move(p1); // 移动
std::cout << "p1 is " << (p1 ? "valid" : "null") << "\n";
std::cout << "p3.use_count: " << p3.use_count() << "\n";
}
运行结果示例:
Node(10) constructed
use_count: 2
use_count after block: 1
p1 is null
p3.use_count: 1
Node(10) destroyed
5. 常见坑与优化
- 裸指针泄漏:在构造函数中一定要检查
ptr是否为nullptr。 - 多线程竞争:虽然使用
std::atomic,但仍需注意对ptr_的读写是否存在数据竞争。 - 性能开销:每次拷贝都需要原子递增,若对象频繁拷贝可考虑使用
std::shared_ptr。 - 循环引用:如上所述,弱引用是常见的解决方案;在设计类图时尽量避免。
6. 结语
自定义智能指针可以更好地符合项目需求,但实现时必须细心处理拷贝、移动和计数等细节。上述 RefCountPtr 是一个完整、线程安全且可直接使用的参考实现,能够帮助你在 C++ 项目中快速搭建资源管理层。祝编码愉快!