Effective C++ 读书笔记(七)
7、模板与泛型编程
条款 41:了解隐式接口和编译期多态
- class 和 template 都支持接口和多态。
- 在 class 中接口是显式的,即我们有具体的函数签名。多态则通过 virtual 函数发生在运行期。
- 在 template 中接口是隐式的,基于有效的表达式。多态则是通过 template 的具现化和函数重载解析发生在编译期。
条款 42:了解typename的双重意义
-
声明 template 参数时,前缀关键字 class 和 template 可互换。如:
// 下面两种写法对编译器来说完全相同 template<class T> class Widget; template<typename T> class Widget;
-
请使用 template 标识嵌套从属类型名称,但不能在 base class list(基类列)或 member initialization list(成员初始列)内以它作为基类的修饰符。
-
首先解释 “使用 template 标识嵌套从属类型名称”
template<typename T> void print(const T& container) { // 这样是不行的 // T::const_iterator iter(container.begin()); // 必须明确告诉 C++ 这个 T::const_iterator 是个类型而不是变量 typename T::const_iterator iter(container.begin()); }
也许我们初衷是获取到集合的迭代器,但是由于泛型 T 可能并不是一个集合,它里面没有一个叫做 const_iterator 类型的嵌套类,编译器也可以把 const_iterator 当作一个类的静态变量,所以这里会存在歧义。因此我们必须使用 typename 告诉 C++ 这是一个类型而不是静态变量。
-
typename 被用来标识一个嵌套从属类型名称,然而还有一个例外,即不能在基类列或成员初始列内以它作为基类的修饰符。如:
// Base 是一个基类,Nested 是基类的一个嵌套类 template<typename T> class Derive : public Base<T>::Nested { // base class list 中不允许 typename public: explicit Derive(int x) : Base<T>::Nested(x) // member initialization list 中不允许 typename { typename Base<T>::Nested temp; // 其余情况需要加上 typename } };
-
条例 43:学习访问模板化基类中的名称
-
编译器往往会拒绝在模板化基类中寻找继承而来的名称,因为基类的模板化可能被特化而那个特化版本也许并不会提供某一接口。所以我们可以使用 “this->” 指定,或使用 “using” 告诉编译器假设它存在。
template<typename T> class Derived : public Base { public: // 方式1,告诉编译器 func 位于基类中 using Base<T>::func(); void test() { // 方式2,假设 func 将被继承 this->func(); } };
条例 44:将与参数无关的代码抽离 templates
- Templates 会生成多个 classes 和函数,所以任何 template 代码都不该与某个造成膨胀的 template 参数产生相依关系。
条例45 : 运用成员函数模板接受所有兼容类型
-
请使用成员函数模板生成 “可接受所有兼容类型” 的函数。即完成一些类型的指针向上转型(例如 Derive* -> Base*)。
-
如果声明的成员模板是用于 “泛化 copy 构造” 或 “泛化 assignment 操作” ,你还需要声明正常的 copy 构造和 assignment 操作符。
// 子类 Y 的智能指针指向父类 T 时就不会出问题了 template<class T> class shared_ptr { public: // copy 构造函数 shared_ptr(shared_ptr const& rhs); // 泛化 copy 构造函数 template<class Y> shared_ptr(shared_ptr<Y> const& rhs); // copy assignment shared_ptr& operator=(shared_ptr const& rhs); // 泛化 copy assignment template<class Y> shared_ptr& operator=(shared_ptr<Y> const& rhs); };
条款46:需要类型转换时请为模板定义非成员函数
-
为了让类型转换可能发生于所有实参上,我们需要一个非成员函数(条款 24)。在模板中,为了令这个函数被自动具现化,我们需要将它声明在类内部。为了在类内部声明非成员函数,唯一的办法是令它成为一个 friend。如:
template<typename T> class Rational { public: // 定义在class内的函数都暗自inline,对于复杂函数而言,我们可以令friend函数调用一个辅助函数避免 friend const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); } Rational(int numerator = 0, int denominator = 1) : mNumerator(numerator), mDenominator(denominator) {} int numerator() const { return mNumerator; } int denominator() const { return mDenominator; } private: int mNumerator; // 分子 int mDenominator; // 分母 }; int main() { Rational<int> oneFourth(1, 4); Rational<int> result; result = 2 * oneFourth; // 如果不是 non-member 的形式将不支持这种写法 cout << "numerator: " << result.numerator() << endl; cout << "denominator: " << result.denominator() << endl; }
条款 47 :请使用 traits classes(特性类)表现类型信息
- Traits 不是 C++ 关键字或一个预先定义好的构件,而是一种技术,也是 C++ 程序员共同遵守的协议。它要求对于内置类型和用户自定义类型的表现必须一样好。Traits classes 使得 “类型相关信息” 在编译期可用,它们以 templates 和 “templates 特化” 完成实现。
- 使用重载函数的形式,可以使 Traits classes 在编译期完成对类型的检测。
- 具体详见here
条款 48 : 认识 template 元编程
- 模板元编程(template metaprogramming TMP)可将工作由运行期移往编译期(编译器时间变长了,可执行文件变小了),因而得以实现早期错误侦测和更高的执行效率。上述 traits 就是 TMP 的应用。
- TMP 可被用来生成 “基于政策选择组合” 的客户定制代码,而可用来避免生成对某些特殊类型不适合的代码。