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 conversion 应该通过?
切换 source/target specialization,观察 underlying pointer gate 如何保留 safe upcast、增加 const,并挡住 downcast 与 const stripping。
当前路径
允许: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 若只能同类型复制,就失去了这组基础表达能力。
↡编译器允许从源类型安全形成目标类型、无需显式 cast 的类型关系。 ↡类模板内部自身也是 template、可为不同调用参数独立推导类型的成员函数。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。
↡为每一组源目标类型人工声明转换 overload 导致的组合增长。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。
↡不同 element-type wrappers 指向同一资源时必须共享同一控制记录与销毁次数。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。
↡参数为本类引用的非模板特殊成员,控制同类型对象复制。 ↡constructor template 不抑制 compiler 隐式声明普通 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。
↡候选质量相同时,普通函数通常优先于函数模板的 overload selection 规则。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 语义,而不是盲目追求方便。
转换矩阵必须包含失败案例
至少验证:
- T 到 T 的 copy/move/assignment。
- Derived 到 Base。
- Derived 到 const Base。
- T 到 const T。
- Base 到 Derived 被拒绝。
- const T 到 T 被拒绝。
- private/ambiguous inheritance 被拒绝。
- 多重 wrappers 共享 control block 并只销毁一次。
先预测每个 constructible_from 与 assignable_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 共享的所有权身份。
- 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:实现 SmartPtr 跨类型构造。 要允许 Derived 到 Base 和增加 const,拒绝反方向。
- 问题 2:只声明 generalized copy constructor 后,同类型复制走了 compiler 生成版本。 修复 ownership bug。
- 问题 3:跨类型 assignment 先释放旧资源再 retain 新资源。 当两者共享 control block 时分析风险。