提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
-
-
- [简化版 `unique_ptr` 核心实现](#简化版
unique_ptr核心实现) - 核心原理拆解(对应代码)
- 测试代码(验证核心逻辑)
- 总结
- [简化版 `unique_ptr` 核心实现](#简化版
-
我们平时自己写的类只有实现了移动构造函数,并且再移动构造函数内正确书写移动构造逻辑,我们的这个类才真正的能够按照移动语义做事,真正发挥移动语义的作用,同理独占指针是一个模板类是吧,它本身是不是就是进制拷贝构造和赋值运算符,并且实现了移动构造,由此形成了自身的特点,不能拷贝,只能够移动,你可以大致用代码描述下独占指针实现,伪代码也行主要说明白原理
你的理解完全正确!核心结论先明确:
- 自定义类要发挥移动语义的价值,必须显式/隐式实现移动构造函数 和移动赋值运算符,并在其中完成"资源所有权转移+原对象置空"的逻辑;
std::unique_ptr本质是一个模板类 ,它的核心实现逻辑就是:禁用拷贝构造/拷贝赋值 ,实现移动构造/移动赋值,同时结合 RAII 机制自动释放资源,最终实现"独占所有权、仅可移动"的特性。
下面用简化版伪代码 还原 unique_ptr 的核心实现逻辑(省略了数组版本、自定义删除器等扩展功能,只保留核心原理),帮你直观理解:
简化版 unique_ptr 核心实现
cpp
// 模板类:支持任意类型的动态内存管理
template <typename T>
class MyUniquePtr {
public:
// 1. 构造函数:接管裸指针的所有权(RAII:资源获取即初始化)
explicit MyUniquePtr(T* ptr = nullptr) : m_ptr(ptr) {}
// 2. 禁用拷贝构造函数:保证独占性(C++11用delete)
MyUniquePtr(const MyUniquePtr& other) = delete;
// 3. 禁用拷贝赋值运算符:避免所有权被拷贝
MyUniquePtr& operator=(const MyUniquePtr& other) = delete;
// 4. 移动构造函数:转移所有权(核心!)
// 参数是右值引用(&&),接收临时对象/被move的对象
MyUniquePtr(MyUniquePtr&& other) noexcept {
// 第一步:接管对方的资源(指针)
m_ptr = other.m_ptr;
// 第二步:将原对象的指针置空,避免析构时重复释放
other.m_ptr = nullptr;
}
// 5. 移动赋值运算符:转移所有权(核心!)
MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
// 第一步:先释放当前对象持有的资源(避免内存泄漏)
if (this != &other) { // 防止自赋值
delete m_ptr;
// 第二步:接管对方的资源
m_ptr = other.m_ptr;
// 第三步:原对象置空
other.m_ptr = nullptr;
}
return *this;
}
// 6. 析构函数:自动释放资源(RAII的关键)
~MyUniquePtr() {
delete m_ptr; // 独占指针析构时,释放持有的动态资源
}
// ========== 模拟指针的核心行为 ==========
// 重载解引用运算符:*ptr
T& operator*() const {
return *m_ptr;
}
// 重载箭头运算符:ptr->func()
T* operator->() const {
return m_ptr;
}
// ========== 辅助方法 ==========
// 手动释放资源(可选)
void reset(T* new_ptr = nullptr) {
delete m_ptr;
m_ptr = new_ptr;
}
// 转移所有权(返回裸指针,原对象置空)
T* release() {
T* temp = m_ptr;
m_ptr = nullptr;
return temp;
}
// 获取内部裸指针(谨慎使用)
T* get() const {
return m_ptr;
}
private:
T* m_ptr; // 核心:封装的裸指针,唯一持有资源所有权
};
核心原理拆解(对应代码)
-
独占性的保证:禁用拷贝
MyUniquePtr(const MyUniquePtr&) = delete;和operator=(const MyUniquePtr&) = delete;直接禁用了拷贝构造和拷贝赋值,从语言层面杜绝了"多个MyUniquePtr持有同一个资源"的可能,这是"独占"的核心。
-
移动语义的落地:所有权转移
- 移动构造/移动赋值的参数是右值引用(&&) ,只能接收临时对象(如函数返回值)或被
std::move标记的对象; - 移动时做两件关键事:
- 接管对方的指针(
m_ptr = other.m_ptr); - 把原对象的指针置空(
other.m_ptr = nullptr),避免原对象析构时重复释放资源。
- 接管对方的指针(
- 移动构造/移动赋值的参数是右值引用(&&) ,只能接收临时对象(如函数返回值)或被
-
RAII 自动释放:无需手动 delete
- 析构函数
~MyUniquePtr()会自动调用delete m_ptr,只要MyUniquePtr对象生命周期结束(如出作用域、被销毁),就会自动释放资源,彻底解决裸指针"忘记 delete"的问题。
- 析构函数
-
模拟指针行为:易用性
- 重载
*和->运算符,让MyUniquePtr能像裸指针一样使用(ptr->func()、*ptr),降低使用成本。
- 重载
测试代码(验证核心逻辑)
cpp
// 测试类
class Product {
public:
~Product() { std::cout << "Product destroyed\n"; }
};
int main() {
// 1. 构造:接管资源
MyUniquePtr<Product> p1(new Product());
// 2. 拷贝:编译失败(已禁用)
// MyUniquePtr<Product> p2 = p1;
// 3. 移动:合法,转移所有权
MyUniquePtr<Product> p2 = std::move(p1);
// p1 已被置空,p2 持有资源
// 4. 函数返回:自动移动(临时对象)
auto createProduct() {
return MyUniquePtr<Product>(new Product());
}
MyUniquePtr<Product> p3 = createProduct(); // 合法,移动构造
return 0; // p3、p2 析构,自动释放资源
}
总结
unique_ptr的核心实现逻辑:禁用拷贝 + 实现移动 + RAII 自动释放;- 移动语义是
unique_ptr实现"独占且可转移"的关键:通过右值引用接管资源,同时置空原对象,保证所有权唯一; - 自定义类要发挥移动语义价值,需像
unique_ptr这样:实现移动构造/赋值,在其中完成"资源转移+原对象置空",避免浅拷贝问题。
这也是为什么说"移动语义是 unique_ptr 的灵魂"------没有移动语义,就无法在保证"独占性"的前提下实现所有权转移,只能像老旧的 auto_ptr 那样靠"劫持拷贝语义"模拟,极易出错。