C++ / a working model

86 / 103   ·   C++11   ·   约 12 分钟

值复制与共享所有权:同样的复制,不同的承诺

先记住这句话

复制一个对象究竟复制数据,还是复制访问同一数据的入口,必须由类型接口讲清楚。对照 vector 的独立值与 shared_ptr 的共享对象,并用 weak_ptr 验证观察者不会延长资源寿命。

本篇内容
  1. 先决定复制在业务上是什么意思
  2. 观察者需要承认对象可能消失
  3. 选择最小的所有权机制
  4. 运行示例
  5. 动手练习
READING EVIDENCE / 已读部分正文

C++ Primer

已按连续区间阅读第1—19章全部可提取正文、代码、习题、术语及附录A与索引,不是只读样章。附录A.1图像表已补读PDF1057—1063。研究副本仍有仅以图像存在的表格、图示与代码未逐一恢复,部分代码转录截断;因此不声称原版所有图文完整通读。练习未执行。

查看版本、实际阅读范围与原文入口 →

先决定复制在业务上是什么意思

书中动态内存与复制控制紧密相连:语言能逐成员复制,却不能替你判断成员代表独立数据还是共享责任。vector 的复制得到独立序列,修改副本不改变原序列;shared_ptr 的复制则增加同一对象的拥有者,不会自动复制所指对象。

两者都是真正的复制,只是复制层次不同。设计接口时应先回答“调用者拿到副本以后是否还能影响我”,再选择表示。不能因为智能指针负责释放,就默认它同时提供值语义;也不应为了避免手工析构,把原本独占或直接存储的数据无条件改成共享。

观察者需要承认对象可能消失

weak_ptr 保存的是可检测的观察关系,不计入强拥有者数量。每次真正使用前调用 lock,得到一个局部 shared_ptr;只要这个局部拥有者还活着,使用期间对象就不会因其他拥有者退出而消失。lock 失败时,接口必须定义缺失的处理方式。

示例先在内部作用域复制强拥有者,确认两者访问同一个 vector;退出后对象仍由外层变量拥有。最后一个强拥有者离开时,观察者变为过期。这比记住一个具体 use_count 数字更接近业务契约,也避免把调试计数误当成线程同步方案。

选择最小的所有权机制

值对象优先直接保存值;确有单一转移责任时才考虑 unique_ptr;多个独立主体真的需要共同维持寿命时再使用 shared_ptr。weak_ptr 解决观察和部分环形所有权问题,不会自动发现或拆除所有强引用环。

本例使用 C++11 已有的设施,C++20 仍适用。没有演示并发:控制块支持不同智能指针实例的某些并发操作,不代表被指向的 vector 自动线程安全。书中的手写容器与分配器用于理解机制,旧版本 allocator 成员和部分复制消除解释需按当前标准重新判断,不能原样当现代库实现。

容易答错的地方

  • shared_ptr 的复制不深复制所指对象;两个接口可能在修改同一数据。
  • weak_ptr::lock 得到的局部强引用会暂时延长寿命;expired 的检查不能替代随后使用时的 lock。

运行一个例子

最低标准 C++11 · 完整程序 · 下载 .cpp

#include <cassert>
#include <memory>
#include <vector>
int main() {
    std::vector<int> original{1, 2};
    auto independent = original;
    independent[0] = 9;
    assert(original[0] == 1 && independent[0] == 9);
    std::weak_ptr<std::vector<int>> observer;
    {
        auto owner = std::make_shared<std::vector<int>>(original);
        observer = owner;
        {
            auto shared = owner;
            (*shared)[0] = 7;
            assert((*owner)[0] == 7);
        }
        auto pinned = observer.lock();
        assert(pinned && (*pinned)[0] == 7);
    }
    assert(observer.expired());
    assert(!observer.lock());
}

在本地编译

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread books-cpp-primer.cpp -o example && ./example

预期结果

预期:正常退出、无输出;所有 assert 通过。

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

如果希望 shared 的修改不影响 owner,应该怎样建立副本?

查看参考答案

把 auto shared = owner 改为 auto shared = std::make_shared<std::vector<int>>(*owner)。这先复制 vector 值,再创建独立的拥有关系。修改 (*shared)[0] 后,(*owner)[0] 应保持1;不要从 owner.get() 构造另一个 shared_ptr,那会产生独立控制块并重复删除同一对象。

继续查证

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

回到目录