Item 45:用 member function templates 接受所有兼容类型

对齐 Effective C++ 第三版 Item 45:为 SmartPtr 编写 generalized copy constructor 与 assignment,以底层 pointer conversion 限定兼容类型,并区分 member templates 和普通 copy special members 的独立职责。

学习目标

  • 能解释为什么 SmartPtr<Derived> 应转换为 SmartPtr<Base>,并判断 downcast、增加 const 与去 const 的合法方向
  • 能实现受底层 pointer conversion 约束的 generalized copy constructor 与 converting assignment
  • 能区分 member function template 和普通 copy special members,设计同类型与跨类型的完整测试矩阵
SmartPtr<U> → SmartPtr<T> compatibility mapcandidate → pointer gate → ownership identitymember function templatestemplate<class U>all compatible typesgeneralized copy constructorderive U from sourcecandidate ≠ permissionsmart pointer conversionU* → T* gateretain same control blockcompatible type conversion is directionalBottom → Topadd constTop ↛ Bottomconst T ↛ Twrapper 复用原始 pointer 的安全边界,不扩张隐式权限
先形成 member template candidate,再让底层 pointer conversion 决定哪一条 SmartPtr 边可用;ownership identity 必须和 pointer 一起传递。

兼容类型实验

先预测:哪一条 SmartPtr conversion 应该通过?

切换 source/target specialization,观察 underlying pointer gate 如何保留 safe upcast、增加 const,并挡住 downcast 与 const stripping。

当前路径

SmartPtr‹Bottom›SmartPtr‹Top›

允许Bottom* 可以隐式转换为 Top*;wrapper 只复制同一 control block 的 ownership。

安全 upcast · conversion matrix

underlying pointer gate 通过;generalized copy constructor retain 同一 control block。

从原始指针的兼容转换开始

class Top {};
class Middle : public Top {};
class Bottom : public Middle {};
 
Bottom* bottom = new Bottom;
Middle* middle = bottom;
Top* top = bottom;
const Top* readonlyTop = bottom;

原始 pointers 天然支持 public inheritance 的 upcast,也允许增加 const。一个通用 smart pointer 若只能同类型复制,就失去了这组基础表达能力。

Item 45 的原则是 Use member function templates to accept all compatible types(用成员函数模板接受所有兼容类型)。

先预测:若 SmartPtr<Bottom> 可转成 SmartPtr<Top>,反方向是否也应自动成立?wrapper 不应赋予原始 pointer 本来没有的隐式权限。

单一类型 copy constructor 不够

template<class T>
class SmartPtr {
public:
    SmartPtr(const SmartPtr& other);
};

这个 constructor 的参数实际是 const SmartPtr<T>&,只处理相同 T。SmartPtr<Bottom>SmartPtr<Top> 是两个不同 class types,普通 copy constructor 无法连接它们。

若为每一对层级手写 overload,类型数量增长后转换边会组合爆炸,也无法覆盖用户未来定义的 derived types。

generalized copy constructor

template<class T>
class SmartPtr {
public:
    template<class U>
    SmartPtr(const SmartPtr<U>& other);
};

U 在调用时从 source 推导,因此一个 declaration 可表达任意 SmartPtr<U> -> SmartPtr<T> 候选。但“任意候选”不等于“任意转换都合法”。

SmartPtr<Bottom> bottom;
SmartPtr<Top> top(bottom); // desired
 
SmartPtr<Top> source;
// SmartPtr<Bottom> invalid(source); // must reject

若 constructor body 只是共享无类型 control block,却没有验证 U/T 关系,危险 downcast 也可能被接受。

让 held pointer 执行兼容性检查

template<class T>
class SmartPtr {
public:
    template<class U>
    SmartPtr(const SmartPtr<U>& other)
        : heldPtr_(other.get()) {}
 
    T* get() const noexcept { return heldPtr_; }
 
private:
    T* heldPtr_ = nullptr;
};

U* -> T* 合法,member initializer 成功;否则实例化失败。Bottom-to-Top 和 T-to-const-T 可用,Top-to-Bottom 与 const-T-to-T 被拒绝。

用现代 constraints 把错误移到接口

初始化失败能保证安全,但诊断可能深入 constructor body。C++20 可直接约束成员模板。

template<class T>
class SmartPtr {
public:
    template<class U>
        requires std::convertible_to<U*, T*>
    SmartPtr(const SmartPtr<U>& other)
        : heldPtr_(other.get()) {}
 
private:
    T* heldPtr_ = nullptr;
};

constraint 不改变 Item 45 的本质,只把 underlying pointer gate 提前到 candidate selection,改善 diagnostics 和 type traits 查询。

static_assert(std::constructible_from<SmartPtr<Top>, SmartPtr<Bottom>>);
static_assert(!std::constructible_from<SmartPtr<Bottom>, SmartPtr<Top>>);

访问另一个 specialization 的内部状态

不同 specialization 是不同 classes,SmartPtr<T> 不自动拥有 SmartPtr<U> 的 private access。公开 get() 是一种边界;也可声明所有 specializations 互为 friends。

template<class T>
class SmartPtr {
    template<class>
    friend class SmartPtr;
 
    template<class U>
        requires std::convertible_to<U*, T*>
    SmartPtr(const SmartPtr<U>& other)
        : heldPtr_(other.heldPtr_) {}
 
    T* heldPtr_ = nullptr;
};

公开 get 降低内部耦合,也为 pointer-like API 提供观察;friend 可直接共享 control block,但会把 specializations 的 representation 紧密绑定。

ownership 语义必须与 pointer 类型一起转换

真实 smart pointer 不只保存 T pointer,还保存 deleter、control block、alias offset 或 allocator。跨类型构造必须共享正确 ownership,而不是创建第二个独立 owner。

template<class U>
    requires std::convertible_to<U*, T*>
SmartPtr(const SmartPtr<U>& other) noexcept
    : heldPtr_(other.heldPtr_), control_(other.control_) {
    retain(control_);
}

测试必须确认 Top/Bottom wrappers 销毁后资源只释放一次,并由能正确销毁动态对象的 deleter 执行。

member template 不会成为 copy constructor

这是 Item 45 最容易漏掉的规则:constructor template 即使 U 可推导为 T,也不是语言定义的 copy constructor。

template<class T>
class SmartPtr {
public:
    SmartPtr(const SmartPtr& other);
 
    template<class U>
        requires std::convertible_to<U*, T*>
    SmartPtr(const SmartPtr<U>& other);
};

若同类型 copy 需要 retain control block、线程同步或自定义 exception guarantee,就必须显式声明;不能以为 generalized copy constructor 已接管。

assignment 同样有两条路径

SmartPtr& operator=(const SmartPtr& rhs);
 
template<class U>
    requires std::convertible_to<U*, T*>
SmartPtr& operator=(const SmartPtr<U>& rhs);

普通 copy assignment 与 converting assignment 独立。二者都要处理 self-assignment、先 retain 后 release、异常安全和旧资源销毁;同类型调用通常优先选择 non-template overload。

explicit 还是 implicit

若底层 U* -> T* 是普通隐式 pointer conversion,smart wrapper 通常也应允许隐式转换,以便传递 SmartPtr<Derived> 给接收 SmartPtr<Base> 的函数。

void consume(SmartPtr<Top> value);
SmartPtr<Bottom> child;
consume(child);

但 ownership model、成本或生命周期变化较大时,可把 constructor 设为 explicit。原则是模拟安全原始 pointer 语义,而不是盲目追求方便。

转换矩阵必须包含失败案例

至少验证:

  1. T 到 T 的 copy/move/assignment。
  2. Derived 到 Base。
  3. Derived 到 const Base。
  4. T 到 const T。
  5. Base 到 Derived 被拒绝。
  6. const T 到 T 被拒绝。
  7. private/ambiguous inheritance 被拒绝。
  8. 多重 wrappers 共享 control block 并只销毁一次。

先预测每个 constructible_fromassignable_from 结果,再编译;运行期配合析构计数器和 sanitizer 验证 ownership。

小结

  • member function templates 让一个 class template specialization 接受任意候选 U
  • generalized copy constructor 连接 SmartPtr<U>SmartPtr<T>,但必须验证 U pointer 到 T pointer 的兼容性
  • safe upcast 与增加 const 应通过,downcast 和去 const 必须拒绝
  • 真实 smart pointer 还必须共享 control block,不能从 raw address 新建独立 owner
  • constructor/assignment templates 不会抑制普通 copy special members,二者都要明确设计
  • constraints 改善候选边界和诊断,正反 conversion matrix 证明接口没有放宽类型安全

名词解释

本章出现的专业名词,用大白话再讲一遍。

compatible type conversion

无需 cast 的安全类型转换。

member function template

类中可独立推导参数的函数模板。

same-specialization copy

相同模板实参对象间的复制。

conversion overload explosion

手写所有转换边导致的组合增长。

generalized copy constructor

接受另一个 specialization 的构造模板。

underlying pointer gate

用底层 pointer conversion 验证兼容性。

smart pointer upcast

derived wrapper 到 base wrapper 的转换。

smart pointer const stripping

经 wrapper 非法移除 const。

pointer-convertibility constraint

源 pointer 可转目标 pointer 的约束。

conversion constructibility test

编译期验证转换构造边界。

cross-specialization friendship

不同实例互访 private 状态的友元。

cross-type ownership identity

跨类型 wrappers 共享的所有权身份。

shared control-block transfer

复制同一控制块并增加引用。

ordinary copy constructor

控制同类型复制的非模板特殊成员。

special-member non-suppression

成员模板不抑制普通特殊成员生成。

generalized copy assignment

兼容 specializations 间的赋值模板。

non-template overload preference

同等匹配时优先普通函数。

conversion explicitness policy

是否允许自动 wrapper 转换的策略。

smart pointer conversion matrix

覆盖类型与 ownership 的正反测试表。

练习

  1. 问题 1:实现 SmartPtr 跨类型构造。 要允许 Derived 到 Base 和增加 const,拒绝反方向。
  1. 问题 2:只声明 generalized copy constructor 后,同类型复制走了 compiler 生成版本。 修复 ownership bug。
  1. 问题 3:跨类型 assignment 先释放旧资源再 retain 新资源。 当两者共享 control block 时分析风险。

资料与写作方式声明

本章以Effective C++, Third Edition, Item 45权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…