Effective C++ 学习笔记 第七章:模板与泛型编程

第一章见 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();  // 调用传入模板参数的那个类
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值