第一章见 Effective C++ 学习笔记 第一章:让自己习惯 C++
第二章见 Effective C++ 学习笔记 第二章:构造、析构、赋值运算
第三章见 Effective C++ 学习笔记 第三章:资源管理
第四章见 Effective C++ 学习笔记 第四章:设计与声明
第五章见 Effective C++ 学习笔记 第五章:实现
第六章见 Effective C++ 学习笔记 第六章:继承与面向对象设计
第七章见 Effective C++ 学习笔记 第七章:模板与泛型编程
第八章见 Effective C++ 学习笔记 第八章:定制 new 和 delete
第九章见 Effective C++ 学习笔记 第九章:杂项讨论
文章目录
条款 41:了解隐式接口和编译期多态
Understand implicit interfaces and compile-time polymorphism.
隐式接口和编译器多态这两个概念,是通过C++ 的模板(和重载机制)而引入的。
首先说编译期多态。一种实现是重载,即在编译期间表现出的多态,在编译期就可以静态绑定到实际的对象和函数中。另一种就是模板,如函数模板和类模板。
一个简单的函数模板实现:
template<typename T>
void fun(T& w) {
if (w.size() > 10) {
T temp(w);
temp.swap(w); // 一些无意义的代码
}
}
这样一个函数,在编译期间,通过传入的模板参数 T,来实现静态绑定,这也叫编译期多态。
运行期多态不必多说,由 virtual 继承实现。
隐式接口是由模板引入的。和它对应的是显式接口,也就是如类结构中的 public 声明。隐式接口是通过表达式合法性约束实现的。
还以上边例子来说,函数 fun 中的参数 T 类型是一个隐式接口,它约束了其必须有 size(),有拷贝赋值操作,有 swap 函数等。这些约束所组成了接口约束,便是隐式接口约束。
和显示接口一样,如果约束不是合法的,它们都会在编译期间被提示出来。
总结
- classes 和 templates 都支持接口(interfaces)和多态(polymorphism)。
- 对 classes 而言,接口是显式的(explicit),以函数签名为中心。多态则是通过 virtual 函数发生在运行期。
- 对 templates 而言,接口是隐式的(implicit),以有效表达式为依据。多态则是通过 template 具现化和函数重载解析发生在编译期。
条款 42:了解 typename 的双重意义
Understand the two meanings of typename.
typename 的使用,一般是大家不太注意到的地方,我习惯于在模板上用 typename,本条款对 typename 进行了详细的说明。
首先,以下两种写法,是完全一致的:
template<class T> class Widget;
template<typename T> class Widget;
class 替代 typename 是旧版写法。
话题 1:typename 作为嵌套从属名称时导致的问题
下例中:
template<typename C>
void p(const C& c) {
C::const_iterator *x; // 似乎 x 是一个 C::const_iterator 类型的指针
}
虽然我们知道 C::const_iterator 一定是个类型,那只是我们的直觉,编译器可能会有其他想法。比如,若 C 刚好有个 const_iterator 的静态成员,而同时 x 又是一个全局变量时,编译器可以认为这的操作是静态变量 C::const_iterator 和全局变量 x 的乘法操作。
typename 参与某个类型的声明时,被称为从属关系;typename 作为有嵌套关系时,就像 C::const_iterator,被称作嵌套从属关系,如果没有嵌套,则是非嵌套从属关系。当 typename 是嵌套从属关系时,可能会出现上边例子的问题。
所以, C++ 中要求,对于 typename 作为嵌套从属关系时,必须使用 typename 作为前缀来声明这是一个类型,如果不使用 typename 声明,则表示一个非类型。
template<typename C>
void p(const C& c) {
C::const_iterator iter(); // 这是一个非类型,会出错
typename C::const_iterator iter(); // 这个才被看做类型
typename C c; // 不应该加
}
注意上例最后一条,typename 只用来修饰有嵌套从属关系的语句,如果不是嵌套从属关系,则不应该加。
话题 2:有一个例外
对于 base classes list 内的嵌套从属关系、成员初始化列表中的嵌套从属关系,不能加 typename 修饰。
template<typename T>
class D : public B<T>::Nested {
// base class list,,虽然有 T 下的嵌套从属关系,不能用 typename 修饰
public:
explicit D(int x) : Base<T>::Nested(x); // 成员初始化列表,也不能用 typename 修饰
};
总结
- 声明 template 参数时,前缀关键字 class 和 typename 可以互换。
- 请使用关键字 typename 标识嵌套从属关系名称;但不得在 base class list 或 成员初始化列表内用它标识嵌套从属关系。
条款 43:学习处理模板化基类内的名称(重要)
Know how to access names in templatized base classes.
先解释下什么叫模板化基类。我们定义一个模板化的类,然后用另一个模板化的类基于前一个类来派生,那么前者就叫做模板化基类(templatized base class),同理后者就叫做模板化派生类。
class Ca {
public:
void fun1();
void fun2();
};
class Cb {
public:
void fun1();
void fun2();
};
// 下边是个模板类,将作为模板化基类使用,模板参数将设计为传入 Ca 和 Cb
template<typename C>
class MS {
public:
void sC() {
C c;
c.fun1(); // 调用传入模板参数的那个类内的函数
}
};
以上例子没问题,现在,我们将 MS 作为模板化基类:
template<typename C>
class D : public MS<C> {
public:
void sCD() {
// 代码经常会这样设计,用来调用到基类的函数
sC(); // 编译器会在这里报错
}
};
上边代码,编译器不允许通过,原因是编译器找不到一个 sC() 的实现。虽然我们看起来知道 sC() 一定是在 C 的某个具现化类中,但编译器不允许,因为也许不一定在呢,编译器不会刻意进入可能的模板化基类中查看 sC() 的合法性。
话题 1:引用模板化基类中定义名称的解决方式
为了解决这个问题,有三种办法:
第一种是使用 this 指针:
template<typename C>
class D : public MS<C> {
public