
继续讨论C++ idioms wikibooke这本书, Coercion by Member Template惯用法,意图是To increase the flexibility of a class template's interface by allowing the class template to participate in the same implicit type conversions (coercion) as its parameterizing types enjoy., 举例说明?
Coercion by Member Template(通过成员模板进行类型转换) 这个 C++ 惯用法的目的是 增强类模板接口的灵活性,使其能够与参数化类型(template parameters)享受相同的隐式类型转换(coercion)。具体来说,当类模板的参数化类型支持某种类型转换时,该类模板的对象也应该支持类似的转换,以提供更直观、灵活的接口。
简单讲就是如果T可以隐式转化为U,那么 smartptr<T>可以转化成smartptr<U>
示例 1:智能指针的隐式转换
一个典型的应用场景是智能指针(类似 std::shared_ptr 和 std::unique_ptr),当我们有一个 SmartPtr<T> 时,如果 T 可以转换为 U,那么 SmartPtr<T> 也应该可以转换为 SmartPtr<U>
示例代码
#include <iostream>
template <typename T>
class SmartPtr {
T* ptr;
public:
explicit SmartPtr(T* p = nullptr) : ptr(p) {}
~SmartPtr() { delete ptr; }
// 拷贝构造(用于相同类型)
SmartPtr(const SmartPtr& other) : ptr(other.ptr ? new T(*other.ptr) : nullptr) {}
// 允许从 SmartPtr<U> 转换到 SmartPtr<T>,如果 U* 能隐式转换为 T*
template <typename U>
SmartPtr(const SmartPtr<U>& other) : ptr(other.ptr ? new T(*other.ptr) : nullptr) {}
// 访问底层指针
T* get() const { return ptr; }
void show() const { std::cout << "SmartPtr holds: " << *ptr << std::endl; }
};
class Base {
public:
virtual void show() const { std::cout << "Base class" << std::endl; }
virtual ~Base() = default;
};
class Derived : public Base {
public:
void show() const override { std::cout << "Derived class" << std::endl; }
};
int main() {
SmartPtr<Derived> derivedPtr(new Derived);
derivedPtr.show();
// 使用成员模板,实现从 SmartPtr<Derived> 到 SmartPtr<Base> 的隐式转换
SmartPtr<Base> basePtr = derivedPtr;
basePtr.show();
return 0;
}
分析 为什么需要成员模板?
SmartPtr<T> 可能需要从 SmartPtr<U> 隐式转换(如果 U* 可以转换为 T)。
例如,Derived 可以隐式转换为 Base*,因此 SmartPtr<Derived> 也应该可以转换为 SmartPtr<Base>。
如何实现?
定义 template <typename U> SmartPtr(const SmartPtr<U>& other);
该构造函数仅在 U* 能够隐式转换为 T* 时有效。
示例效果
SmartPtr<Derived> 能够隐式转换为 SmartPtr<Base>,正如原始指针 Derived* 能隐式转换为 Base* 一样。
示例 2:数学向量类
另一个例子是一个简单的 Vector<T> 类,它应该允许 Vector<int> 自动转换为 Vector<double>,就像 int 可以转换为 double 一样。
#include <iostream>
template <typename T>
class Vector {
T x, y;
public:
Vector(T x, T y) : x(x), y(y) {}
// 允许从 Vector<U> 隐式转换为 Vector<T>,前提是 U 能转换为 T
template <typename U>
Vector(const Vector<U>& other) : x(other.getX()), y(other.getY()) {}
T getX() const { return x; }
T getY() const { return y; }
void show() const { std::cout << "Vector(" << x << ", " << y << ")" << std::endl; }
};
int main() {
Vector<int> vi(3, 4);
vi.show();
// 由于成员模板的存在,这里可以隐式转换为 Vector<double>
Vector<double> vd = vi;
vd.show();
return 0;
}
总结 “Coercion by Member Template” 通过 成员模板构造函数 允许类模板的对象 享受其参数类型的隐式转换,从而:
使智能指针(SmartPtr<T>)可以像普通指针一样转换。
使数学向量(Vector<T>)在不同数值类型之间转换,提升泛型编程的便利性。
这个惯用法在 智能指针、容器、数学运算类 等模板类设计中非常常见。
||=== Build: Debug in Coercion by Member Template (compiler: GNU GCC Compiler) ===| C:\workspace\cpp-exercises\Coercion by Member Template\main.cpp||In instantiation of 'void SmartPtr
::show() const [with T = Derived]':| C:\workspace\cpp-exercises\Coercion by Member Template\main.cpp|40|required from here| C:\workspace\cpp-exercises\Coercion by Member Template\main.cpp|24|error: no match for 'operator<<' (operand types are 'std::basic_ostream ' and 'Derived')|
你的编译错误是由于 std::ostream 不能直接输出 Derived 类型的对象。在 SmartPtr
::show() 方法中,我们尝试使用 std::cout << *ptr;,但 Derived 类没有重载 operator<<,导致编译错误。
解决方案 我们有两种方式来修复这个问题: 方法 1:为 Base 和 Derived 类提供 operator<< 重载
#include <iostream>
template <typename T>
class SmartPtr {
T* ptr;
public:
explicit SmartPtr(T* p = nullptr) : ptr(p) {}
~SmartPtr() { delete ptr; }
// 允许从 SmartPtr<U> 转换到 SmartPtr<T>,如果 U* 能隐式转换为 T*
template <typename U>
SmartPtr(const SmartPtr<U>& other) : ptr(other.get() ? new T(*other.get()) : nullptr) {}
// 访问底层指针
T* get() const { return ptr; }
void show() const {
if (ptr)
std::cout << *ptr << std::endl;
else
std::cout << "SmartPtr is empty" << std::endl;
}
};
// 定义基类
class Base {
public:
virtual void show() const { std::cout << "Base class" << std::endl; }
virtual ~Base() = default;
// 重载 << 让 std::cout 能输出 Base 或其派生类
friend std::ostream& operator<<(std::ostream& os, const Base& obj) {
obj.show();
return os;
}
};
// 定义派生类
class Derived : public Base {
public:
void show() const override { std::cout << "Derived class" << std::endl; }
};
int main() {
SmartPtr<Derived> derivedPtr(new Derived);
derivedPtr.show();
// SmartPtr<Derived> 能够转换为 SmartPtr<Base>
SmartPtr<Base> basePtr = derivedPtr;
basePtr.show();
return 0;
}
方法 2:直接调用 ptr->show()
如果你只想让 SmartPtr
void show() const {
if (ptr)
ptr->show(); // 直接调用 T 的 show() 方法
else
std::cout << "SmartPtr is empty" << std::endl;
}
这样就不需要重载 operator<< 了。
总结
你的编译错误的原因是 std::cout << *ptr; 需要 operator<< 支持,而 Derived 没有实现它。解决方案:
重载 operator<<(推荐) 直接调用 ptr->show()(更简单但更局限) 根据你的需求选择合适的解决方案!
重点解释一下 // SmartPtr
能够转换为 SmartPtr SmartPtr basePtr = derivedPtr; 这两个模板类是如何实现类型转化得?
在 SmartPtr<Derived> 隐式转换为 SmartPtr<Base> 的过程中,主要依赖 成员模板(member template)构造函数,它允许 SmartPtr<T> 之间进行类型转换,只要 T* 可以隐式转换为 U*。
代码分析
// 允许从 SmartPtr<U> 转换到 SmartPtr<T>,如果 U* 能隐式转换为 T*
template <typename U>
SmartPtr(const SmartPtr<U>& other) : ptr(other.get() ? new T(*other.get()) : nullptr) {}
- 代码作用
这是 一个模板构造函数,它使得
SmartPtr<U>可以转换为SmartPtr<T>。 其 前提 是 U* 可以隐式转换为 T*,即 U 是 T 的派生类时才成立。 转换过程 假设:
SmartPtr<Derived> derivedPtr(new Derived);
SmartPtr<Base> basePtr = derivedPtr; // 发生类型转换
我们来分解 SmartPtr<Base> basePtr = derivedPtr; 这句代码的转换过程。
SmartPtr<Base>需要调用合适的构造函数derivedPtr是SmartPtr<Derived>类型basePtr需要的是SmartPtr<Base>由于SmartPtr提供了成员模板构造函数:
template <typename U>
SmartPtr(const SmartPtr<U>& other);
可以匹配 U = Derived,T = Base。
- 模板构造函数的匹配 展开后,相当于调用:
SmartPtr<Base>::SmartPtr<Derived>(const SmartPtr<Derived>& other);
这意味着:
other.get() 返回 Derived* 指针
new T(*other.get()) 变成 new Base(*derivedPtr.get())
这里假设 Base 具有 Derived 的拷贝构造能力(或者是 slicing)
ptr 被赋值为新创建的 Base*。
- U* 到 T* 的隐式转换 如果 U 是 T 的派生类(即 Derived 继承自 Base),则 U* 可以隐式转换为 T*:
Derived* d = new Derived();
Base* b = d; // 这里发生了指针转换
因此,SmartPtr<Derived> 也能转换为 SmartPtr<Base>,只要 我们在 SmartPtr 内部正确处理了 T* 的构造。
总结 关键点:
成员模板构造函数:
template <typename U> SmartPtr(const SmartPtr<U>& other);
允许 SmartPtr<U> 转换为 SmartPtr
由于 Derived* 可以转换为 Base,所以 SmartPtr<Derived> 可以转换为 SmartPtr<Base>。
在 SmartPtr 内部创建新的 T 对象:
ptr = new T(*other.get());
这确保了新 SmartPtr<Base> 仍然拥有自己的 Base* 资源。
这样,我们就成功 让 SmartPtrSmartPtr<Base> 了!
允许
SmartPtr<U>转换为SmartPtr<T>,前提是 U* 可以隐式转换为 T*,这前提是不是必须得,是否 U可以转化为T也行?
在 SmartPtr<U> 转换为 SmartPtr<T> 时,U 能隐式转换为 T 这一前提是 必要的,但并不是唯一可能的条件。理论上,如果 U 可以转换为 T(即存在 U → T 的转换),而不仅仅是 U* → T*,那么 SmartPtr<U> 仍然可能转换为 SmartPtr<T>,但实现方式会有所不同。
两种可能的转换方式 指针类型转换(U → T)** → 适用于 继承关系 值类型转换(U → T) → 适用于 存在用户定义转换 1. 指针类型转换(U → T)** 这种情况发生在 U 继承自 T 时,例如:
class Base { };
class Derived : public Base { };
对于 SmartPtr<Derived> 转换为 SmartPtr<Base>,只要 Derived* 能够隐式转换为 Base*,成员模板就能生效:
template <typename U>
SmartPtr(const SmartPtr<U>& other) : ptr(other.get() ? new T(*other.get()) : nullptr) {}
这里 T = Base,U = Derived,指针的转换 Derived* → Base* 天然支持,因此可以成功编译。
这就是标准的「指针向上转型(upcasting)」,广泛适用于 继承体系。
值类型转换(U → T) 如果 U 不能 直接隐式转换为 T*,但可以 转换为 T 的对象,那么仍然可以让
SmartPtr<U>转换为SmartPtr<T>,但需要修改 SmartPtr 的实现。例如:class Meter { public: explicit Meter(double v) : value(v) {} double getValue() const { return value; } private: double value; };
class Centimeter { public: explicit Centimeter(double v) : value(v) {}
// 用户定义转换 Centimeter → Meter operator Meter() const { return Meter(value / 100.0); } double getValue() const { return value; }private: double value; };
Centimeter 提供了 operator Meter(),因此 可以转换为 Meter,但 Centimeter* 不能转换为 Meter*,因为两者是不同的类,没有继承关系。
如果 SmartPtr<U> 采用如下实现:
template <typename U>
SmartPtr(const SmartPtr<U>& other) : ptr(other.get() ? new T(*other.get()) : nullptr) {}
那么:
SmartPtr<Centimeter> cmPtr(new Centimeter(150));
SmartPtr<Meter> mPtr = cmPtr; // 依赖 Centimeter → Meter 的转换
在 SmartPtr 内部,other.get() 是 Centimeter,它可以 显式或隐式转换为 Meter,因此 new T(other.get()) 变成了:
new Meter(static_cast<Meter>(*other.get()))
这样,即使 指针不能转换,但由于 U → T 存在转换,也可以完成 SmartPtr → SmartPtr
结论
必须满足至少一种转换条件
如果 U* 可以转换为 T(即 U 继承自 T),那么 SmartPtr<U> 自然能转换 为 SmartPtr<T>,无需额外处理。
如果 U 可以转换为 T(如 Centimeter → Meter),那么 SmartPtr<U> 仍然可以转换为 SmartPtr<T>,但 SmartPtr 的实现需要 new T(other.get()) 这种方式来完成转换。
总结
在 SmartPtr<U> 转换为 SmartPtr<T> 的过程中,U* 能隐式转换为 T* 并不是唯一的必要条件,但至少需要:
U* → T*(指针转换):常见于继承关系
U → T(值转换):常见于用户定义转换
如果 U* 不能转换为 T*,但 U 可以转换为 T,则 SmartPtr<U> 仍然可以转换为 SmartPtr<T>,但 SmartPtr 需要支持 new T(*other.get()) 这种方式。

评论