38 / 80 · C++14 · 约 9 分钟
智能指针选择 Ownership vocabulary
先考虑直接值成员和容器;确需动态生存期时,默认用 unique_ptr 表达独占拥有,仅在多个参与者都必须延长生存期时使用 shared_ptr。观察者采用引用、裸指针或 weak_ptr,不要把所有访问都升级为拥有。
动态分配之前先选值语义
一个类若始终拥有一个简单成员,直接把该成员作为值保存通常最明确。动态数组先选 vector,而不是为每个元素分配一个指针。只有需要多态、稳定的独立对象位置、可选的动态资源或跨作用域转移等理由时,才进一步考虑智能指针。
unique_ptr 表示某一时刻只有一个拥有者,不能复制但可以移动。shared_ptr 表示多个拥有者共同决定对象销毁时机,复制会加入同一所有权组。两者选择由业务生命周期决定,不能把 shared_ptr 当成省去分析责任的万能保险。
访问者不必成为拥有者
短暂读取对象的函数接收 const T& 即可;可选观察者可以使用 T*。这些借用不延长生存期,调用者要保证目标仍活着。需要观察共享对象、又不想阻止回收时,用 weak_ptr,并在实际访问时通过 lock 取得临时拥有权。
示例把 unique_ptr 移入 vector,源指针变空,vector 接管销毁责任。插入前取得的原始指针仍指向同一个动态对象,因为移动的是 unique_ptr,不是其管理的整数。不过元素被删除或拥有者 reset 后,观察指针立刻失效。
工厂与删除器一起考虑
优先使用 make_unique 和 make_shared,使创建与管理成为一个完整操作。与 C 接口合作时,必须根据资源的释放协议提供删除器;由 malloc 得到的存储需要 free,不能交给默认使用 delete 的 unique_ptr。删除器本身也是所有权类型契约的一部分。
unique_ptr<T[]> 能管理 new[] 数组,但不记录长度,不能替代具有尺寸信息的 vector。通过基类 unique_ptr 删除派生对象时,一般要求基类具备合适的虚析构函数。智能指针能自动执行正确的策略,却不能把错误的销毁策略变正确。
容易答错的地方
- get 只提供借用地址,不能把它再交给另一个独立拥有型智能指针,否则可能重复释放。
- unique_ptr 的尺寸与删除器类型有关;不要把“必定等于一个裸指针”当成跨平台接口保证。
运行一个例子
最低标准 C++14 · 完整程序 · 下载 .cpp
#include <cassert>
#include <memory>
#include <utility>
#include <vector>
int main() {
auto item = std::make_unique<int>(7);
int* observer = item.get();
std::vector<std::unique_ptr<int>> items;
items.push_back(std::move(item));
assert(!item);
assert(items.front().get() == observer);
assert(*observer == 7);
*items.front() = 9;
assert(*observer == 9);
}
在本地编译
g++ -std=c++14 -Wall -Wextra -Wpedantic -pthread memory-smart-pointers.cpp -o example && ./example预期结果
预期:正常退出、无输出;所有 assert 通过。
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
树结构中父节点独占孩子,而孩子只需要访问父节点,应如何表达两个方向的关系?
查看参考答案
父节点可用 vector<unique_ptr<Node>> 拥有孩子,孩子用 Node* parent 非拥有地观察父节点。必须约束孩子不能独立活过父节点,并在重新挂接时更新 parent。如果设计允许孩子独立共享存活,才需要重新建模,例如共享拥有孩子、用 weak_ptr 观察共享父节点,而不是机械把所有边都改成 shared_ptr。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。