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

本文深入探讨了C++中的模板与泛型编程,涵盖隐式接口、编译期多态、typename的双重意义、处理模板化基类内的名称、模板代码抽离、成员函数模板的应用、类型转换与非成员函数模板、traits类表现类型信息以及模板元编程等关键概念。通过实例解析,阐述了如何有效利用模板提升代码的灵活性和复用性。
摘要由CSDN通过智能技术生成

第一章见 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值