提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 核心原理:`unique_ptr` 禁止拷贝,但允许「移动」
- 用代码验证:拷贝 vs 移动的区别
- 1. 合法场景(移动/返回值优化):你的例子
- 2. 非法场景(尝试拷贝左值):编译失败
- 补充:为什么 `make_unique` 更安全?
- 总结
你想弄明白的核心问题是:unique_ptr本身禁止拷贝赋值,但auto product = createProduct();这句代码却能把函数返回的unique_ptr赋值给变量,这看起来矛盾,其实核心原因是这里不是拷贝,而是 C++ 的移动语义 + 返回值优化。
核心原理:unique_ptr禁止拷贝,但允许「移动」
unique_ptr的设计初衷是独占所有权,所以它明确禁用了拷贝构造函数和拷贝赋值运算符(被声明为delete),但它提供了移动构造函数和移动赋值运算符,专门用来处理「所有权转移」的场景——尤其是针对临时对象(右值)。
具体到这个例子,拆解来看:
createProduct()函数返回的是一个临时的unique_ptr<Product>对象(没有名字的右值);- C++ 标准规定:当用一个右值(临时对象)初始化/赋值另一个
unique_ptr时,编译器会自动调用「移动构造函数」,把临时对象的所有权转移给新对象,而不是拷贝; - 更优的是,编译器还会触发返回值优化(NRVO/NRVO):直接在
main函数的product变量内存位置构造unique_ptr,连移动操作都跳过,效率和直接构造几乎无差别。
用代码验证:拷贝 vs 移动的区别
我们可以通过对比代码,更直观地看到差异:
1. 合法场景(移动/返回值优化):你的例子
#include<memory>#include<iostream>classProduct{public:~Product(){std::cout<<"Product destroyed\n";}};std::unique_ptr<Product>createProduct(){returnstd::unique_ptr<Product>(newProduct());}intmain(){// 合法:接收函数返回的临时unique_ptr(右值),触发移动/返回值优化autoproduct=createProduct();return0;}2. 非法场景(尝试拷贝左值):编译失败
intmain(){autop1=std::make_unique<Product>();// 编译错误!p1是具名的左值,unique_ptr禁止拷贝赋值// auto p2 = p1;// 正确的做法:显式移动(所有权转移)autop2=std::move(p1);// p1失去所有权(变为空),p2获得所有权return0;}补充:为什么make_unique更安全?
你代码里注释提到C++14的make_unique更安全,这里顺带解释:
- 原写法
return std::unique_ptr<Product>(new Product());存在极小的异常安全风险(比如new Product()成功,但unique_ptr构造前抛出异常,导致内存泄漏); make_unique是将「内存分配」和「智能指针对象构造」封装在一个函数里,是原子操作,彻底避免了这种风险,且写法更简洁。
修改后的更优版本:
#include<memory>#include<iostream>classProduct{public:~Product(){std::cout<<"Product destroyed\n";}};// C++14及以上推荐写法std::unique_ptr<Product>createProduct(){returnstd::make_unique<Product>();// 更安全、更简洁}intmain(){autoproduct=createProduct();// 依然是移动/返回值优化,合法return0;}总结
unique_ptr禁止拷贝(左值之间的赋值/构造),但允许移动(右值/临时对象的所有权转移);auto product = createProduct();合法的原因:函数返回的是临时unique_ptr(右值),编译器自动触发移动构造/返回值优化,而非拷贝;- 具名的
unique_ptr若要转移所有权,必须显式使用std::move,否则编译失败。
这也是unique_ptr既保证「独占所有权」,又能灵活转移所有权的核心设计。
std::unique_ptr本身也是个类,里面知识没有拷贝构造函数,但有移动构造函数 createProduct();返回的就是一个右值 这个过程走移动构造 所以没问题 std::unique_ptr本身就是移动语义应用层面的一个很好的例子感觉