本篇笔记包含 Term 41~48。

Term 41:了解隐式接口和编译期多态

  1. classes 和 templates 都支持接口和多态
  2. 对 classes 而言接口时显式的,以函数签名(函数名、参数类型、返回类型)为中心。多态则通过 virtual 函数发生与运行期
  3. 对 templates 参数而言,接口是隐式的,基于有效表达式。多态则是通过 template 具现化和函数重载解析发生与编译期

Term 42:了解 typename 的双重意义

  1. 声明 template 参数时,前缀关键字 class 和 typename 同义
  2. 需要使用关键字 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种方法:

  1. 使用 this->
  2. 使用 using
  3. 明确指明使用 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

  1. Templates 生成多个 classes 和多个函数,所以任何 template 代码都不该与某个造成膨胀的 template 参数产生相依关系
  2. 因非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可消除,做法是以函数参数或 class 成员变量替换 template 参数
  3. 因类型参数(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 表现类型信息

  1. Traits classes 使得“类型相关信息”在编译器可用。它们以 templates 技术、typedef 表示类型和 “templates 特化”完成实现
  2. Traits classes 通过重载,在编译器对类型执行 if...else 测试

Term 48:认识 template 元编程

Template 元编程(TMP)是执行与编译器的过程,通过 template 产生更多源码。TMP 的贡献在于,将一些运行期的工作带到了编译期执行,通常会产生出更加高效的程序,并且在编译期就发现一些问题。TMP 单独来说已经是图灵完全机器,但是编写 TMP 代码会比“一般”C++ 难得多。