本篇笔记包含 Term 41~48。
Term 41:了解隐式接口和编译期多态
- classes 和 templates 都支持接口和多态
- 对 classes 而言接口时显式的,以函数签名(函数名、参数类型、返回类型)为中心。多态则通过 virtual 函数发生与运行期
- 对 templates 参数而言,接口是隐式的,基于有效表达式。多态则是通过 template 具现化和函数重载解析发生与编译期
Term 42:了解 typename 的双重意义
- 声明 template 参数时,前缀关键字 class 和 typename 同义
- 需要使用关键字 typename 标识嵌套从属类型名称;但不得在 base class lists(基类列)或 member initialization list 内以它作为 base class 修饰符
template <typename C> // 可以使用 typename 或者 class
void f(const C& container, // 不能使用 typename
typename C::iterator iter); // 应该使用 typename
template <typename T>
class Drived : public Base<T>::Nested { // 不使用 typename
public:
explicit Derived(int x) : Base<T>::Nested(x) { // 不使用 typename
typename Base<T>::Nested tmp; // 使用 typename
}
};
Term 43:学习处理模板化基类内的名称
派生类继承模板化基类的情况中,如果直接调用基类中的成员,会无法通过编译,因为编译器无法知道具体继承自哪个基类(假如基类被特化,可能根本不包含被调用的成员)。为了解决这个问题,有3种方法:
- 使用
this->
- 使用
using
- 明确指明使用 base class 版本(不推荐,相当于关闭了 virtual)
template <typename Company>
class LoggingMsgSender: public MsgSender<Company> { // MsgSender 中包含 sendClear() 函数
public:
using MsgSender<Company>::sendClear; // 2.方法
void sendClearMsg(const MsgInfo& info) {
sendClear(info); // 调用失败
this->sendClear(info); // 1.方法
MsgSender<Company>::sendClear(info); // 3.方法
}
};
Term 44:将与参数无关的代码抽离 template
- Templates 生成多个 classes 和多个函数,所以任何 template 代码都不该与某个造成膨胀的 template 参数产生相依关系
- 因非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可消除,做法是以函数参数或 class 成员变量替换 template 参数
- 因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representations)的具现类型(instantiation types)共享实现码
Term 45:运用成员函数模板接受所有兼容类型
智能指针是很好用的东西,但是存在一个问题,其模板写法不能支持隐式的类型转换。真实的指针则可以做到,示例就是派生类指针可以隐式转换为基类指针。为了在使用智能指针时也能达到此效果,需要这样编写程序:
template <typename T>
class SmartPtr {
public:
template <typename U>
SmartPtr(const SmartPtr<U>& other) : heldPrt(other.get()) { /*...*/ } // 支持隐式转换的 copy 构造函数模板
T* get() const { return heldPtr; } // 假设通用接口 get() 获取原始指针
private:
T* heldPtr; // 原始指针
};
跟上面的操作一样,copy 赋值操作符也可以进行泛化。需注意,函数模板不会覆盖编译器自动生成的 copy 构造函数和copy 赋值操作符。
Term 46:需要类型转换时请为模板定义非成员函数
Term 24 提到应使用 non-member 函数对所有实参进行类型转换。在使用模板时,情况有一点变化,我们仍然应该编写一个 non-member 函数,然后再 class 中定义 friend 函数并调用外面的 non-member 函数。
template <typename T> class Rational; // 声明类
template <typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs); // 声明 helper
template <typename T>
class Rational {
public:
friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) { // 调用 helper
return doMultiply(lhs, rhs);
}
};
template <typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs) { ... } // 定义 helper
Term 47:请使用 traits classes 表现类型信息
- Traits classes 使得“类型相关信息”在编译器可用。它们以 templates 技术、typedef 表示类型和 “templates 特化”完成实现
- Traits classes 通过重载,在编译器对类型执行 if...else 测试
Term 48:认识 template 元编程
Template 元编程(TMP)是执行与编译器的过程,通过 template 产生更多源码。TMP 的贡献在于,将一些运行期的工作带到了编译期执行,通常会产生出更加高效的程序,并且在编译期就发现一些问题。TMP 单独来说已经是图灵完全机器,但是编写 TMP 代码会比“一般”C++ 难得多。