C++ / a working model

38 / 80   ·   C++14   ·   约 9 分钟

智能指针选择 Ownership vocabulary

先记住这句话

先考虑直接值成员和容器;确需动态生存期时,默认用 unique_ptr 表达独占拥有,仅在多个参与者都必须延长生存期时使用 shared_ptr。观察者采用引用、裸指针或 weak_ptr,不要把所有访问都升级为拥有。

本篇内容
  1. 动态分配之前先选值语义
  2. 访问者不必成为拥有者
  3. 工厂与删除器一起考虑
  4. 运行示例
  5. 动手练习

动态分配之前先选值语义

一个类若始终拥有一个简单成员,直接把该成员作为值保存通常最明确。动态数组先选 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。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录