在现代 C++ 开发中,智能指针是管理动态内存的重要工具。除了标准库中的 std::unique_ptr 和 std::shared_ptr,我们有时也需要根据业务场景自定义一个智能指针。实现自定义智能指针时,拷贝和移动语义的设计尤为关键,它决定了指针在赋值、传参、返回等场景中的行为。本文将通过完整示例说明如何实现一个简单的引用计数型智能指针,并在此基础上实现拷贝构造、拷贝赋值、移动构造、移动赋值等语义。
1. 设计目标
- 引用计数:类似
std::shared_ptr,多份指针共享同一资源,计数递增/递减。 - 异常安全:在出现异常时不泄漏资源。
- 移动语义:移动构造和移动赋值可以“转移”资源而不产生副本,提升性能。
- 线程安全:引用计数使用原子操作,保证在多线程环境下正确。
2. 关键类结构
#include <atomic>
#include <utility>
template<typename T>
class RefCountPtr {
public:
// 构造
explicit RefCountPtr(T* ptr = nullptr);
// 析构
~RefCountPtr();
// 拷贝构造
RefCountPtr(const RefCountPtr& other);
// 拷贝赋值
RefCountPtr& operator=(const RefCountPtr& other);
// 移动构造
RefCountPtr(RefCountPtr&& other) noexcept;
// 移动赋值
RefCountPtr& operator=(RefCountPtr&& other) noexcept;
// 重载操作符
T& operator*() const noexcept;
T* operator->() const noexcept;
T* get() const noexcept;
// 计数查询
size_t use_count() const noexcept;
private:
T* ptr_;
std::atomic <size_t>* ref_count_;
};
2.1 构造与析构
- 构造:创建指针时,
ptr_赋值为传入指针,ref_count_设为新分配的 `atomic `,初始计数为 1。若传入 `nullptr`,则 `ref_count_` 为 `nullptr`。 - 析构:若
ptr_非空,递减计数;计数变为 0 时,删除对象和计数器。
2.2 拷贝语义
拷贝构造/赋值将 ptr_ 复制过去,并递增引用计数。使用 atomic 确保线程安全。移动语义只转移指针和计数指针,然后将源置为 nullptr。
2.3 异常安全
- 构造时若分配计数器失败,抛异常前不影响原始指针。
- 拷贝赋值采用“copy‑and‑swap”模式,先拷贝再交换,确保异常不破坏目标对象。
3. 代码实现
#include <atomic>
#include <utility>
#include <iostream>
template<typename T>
class RefCountPtr {
public:
explicit RefCountPtr(T* ptr = nullptr)
: ptr_(ptr)
{
if (ptr_) {
ref_count_ = new std::atomic <size_t>(1);
} else {
ref_count_ = nullptr;
}
}
~RefCountPtr() {
release();
}
// 拷贝构造
RefCountPtr(const RefCountPtr& other)
: ptr_(other.ptr_), ref_count_(other.ref_count_)
{
increment();
}
// 拷贝赋值
RefCountPtr& operator=(const RefCountPtr& other) {
if (this != &other) {
RefCountPtr temp(other); // 先拷贝
swap(temp); // 再交换
}
return *this;
}
// 移动构造
RefCountPtr(RefCountPtr&& other) noexcept
: ptr_(other.ptr_), ref_count_(other.ref_count_)
{
other.ptr_ = nullptr;
other.ref_count_ = nullptr;
}
// 移动赋值
RefCountPtr& operator=(RefCountPtr&& other) noexcept {
if (this != &other) {
release(); // 先释放自身
ptr_ = other.ptr_;
ref_count_ = other.ref_count_;
other.ptr_ = nullptr;
other.ref_count_ = nullptr;
}
return *this;
}
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
T* get() const noexcept { return ptr_; }
size_t use_count() const noexcept { return ref_count_ ? ref_count_->load() : 0; }
void swap(RefCountPtr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(ref_count_, other.ref_count_);
}
private:
void increment() {
if (ref_count_) {
ref_count_->fetch_add(1, std::memory_order_relaxed);
}
}
void release() {
if (ref_count_) {
if (ref_count_->fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete ptr_;
delete ref_count_;
}
}
}
T* ptr_;
std::atomic <size_t>* ref_count_;
};
4. 使用示例
struct Demo {
int x;
Demo(int v) : x(v) { std::cout << "Demo(" << x << ") constructed\n"; }
~Demo() { std::cout << "Demo(" << x << ") destroyed\n"; }
};
int main() {
RefCountPtr <Demo> p1(new Demo(42));
std::cout << "p1 use_count: " << p1.use_count() << '\n';
{
RefCountPtr <Demo> p2 = p1; // 拷贝构造
std::cout << "after copy, p1: " << p1.use_count() << ", p2: " << p2.use_count() << '\n';
RefCountPtr <Demo> p3;
p3 = std::move(p2); // 移动赋值
std::cout << "after move, p3: " << p3.use_count() << ", p2: " << p2.use_count() << '\n';
} // p2,p3销毁
std::cout << "after block, p1 use_count: " << p1.use_count() << '\n';
}
输出
Demo(42) constructed
p1 use_count: 1
after copy, p1: 2, p2: 2
after move, p3: 2, p2: 0
Demo(42) destroyed
after block, p1 use_count: 1
5. 小结
- 引用计数 通过 `std::atomic ` 实现线程安全。
- 拷贝构造/赋值 递增计数,采用 copy‑and‑swap 以实现异常安全。
- 移动构造/赋值 只转移指针和计数指针,源置为空,性能优越。
- 析构 在计数为 0 时释放资源,防止泄漏。
通过上述实现,你可以根据业务需求进一步扩展,例如实现弱引用(类似 std::weak_ptr)、自定义分配器或在引用计数器上加锁实现更高层次的同步。此自定义智能指针既兼顾了 C++ 标准库的安全特性,又保留了实现细节的灵活性。