Effective C++——条款41,条款42(第7章)

第7章    模板与泛型编程

Templates and Generic Programming

    C++ template 的最初发展动机很直接:使人们得以建立"类型安全"(type-safe)的容器如vector,list和map.容器当然好,但 泛型编程(generic programming)写出的代码和其所处理的对象类型彼此独立,这点相当好.STL算法如for_each,find和merge就是这一类编程的结果.最终人们发现,C++ template 机制自身是一部完整的图灵机:它可以用来计算任何可计算的值.于是导出了模板元编程,创造出"在C++编译器内执行并于编译完成时停止执行"的程序.尽管 template 的应用如此宽广,有一组核心观念一直支撑着所有基于 template 的编程,那些观点便是本章焦点.

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

Understand implicit interfaces and compile-time polymorphism

     面向对象编程世界总是以显式接口(explicit interface)和运行期多态(runtime polymorphism)解决问题.例如,给定这样的 class:
class Widget {
public:
    Widget();
    virtual ~Widget();
    virtual std::size_t size() const;
    virtual void normalize();
    void swap(Widget& other);
    ...
};
    和这样的函数:
void doProcessing(Widget& w) {
    if (w.size() > 10 && w != someNastyWidget) {
        Widget temp(w);
        temp.normalize();
        temp.swap(w);
    }
}
    可以这样说doProcessing内的w:
    由于w的类型被声明为Widget,所以w必须支持Widget接口. 可以在源码中找出这个接口(例如Widget的.h文件中),看看它是什么样,所以称此为一个 显式接口(explicit interface),也就是它在源码中明确可见.
    由于Widget的某些成员函数是 virtual,w对那些函数的调用将表现出运行期多态(runtime polymorphism),也就是说 将于运行期根据w的动态类型(详见条款37)决定调用哪一个函数.
     Templates以及泛型编程的世界,与面向对象有根本上的不同.在这个世界中显式接口和运行期多态仍然存在,但重要性降低.反倒是 隐式接口(implicit interface)和编译器多态(compile-time polymorphism)变得重要.若想知道原因,看看将doProcessing从函数转变为函数模板(function template)时发生什么事情:
template<typename T>
void doProcessing(T& w) {
    if (w.size() > 10 && w != someNastyWidget) {
        T temp(w);
        temp.normalize();
        temp.swap(w);
    }
}
    现在怎么说doProcessing内的w呢?
     w必须支持哪一种接口,系由 template 中执行于w身上的操作来决定.本例看来w的类型T好像必须支持size,normalize和swap成员函数,copy构造函数(用以建立temp),不等比较. 重要的是,这一组表达式(对此 template 而言必须有效编译)便是T必须支持的一组隐式接口(implicit template).
    凡涉及w的任何函数调用,例如 operator>和 operator!=,有可能造成 template 具现化(instantiated),使这些调用得以成功.这样的 具现行为发生在编译期."以不同的template参数具现化function template"会导致不同的函数,这便是所谓的编译期多态(compile-time polymorphism).
    编译器多态和运行期多态之间的差异
, 类似于"哪一个重载函数被调用"(发生在编译期)和"哪一个virtual函数该被绑定"(发生在运行期)之间的差异.显式接口和隐式接口的差异就比较新颖,需要更多更贴近的说明和解释.
    通常 显式接口由函数的签名式(也就是函数名称,参数类型,返回类型)构成.例如Widget class:
class Widget {
public:
    Widget();
    virtual ~Widget();
    virtual std::size_t size() const;
    virtual void normalize();
    void swap(Widget& other);
};
    其 public 接口由一个构造函数,一个析构函数,函数size,normalize,swap以及其参数类型,返回类型,常量性构成.当然也包括编译器产生的copy构造函数和copy assignment操作符(详见 条款5).
    隐式接口就完全不同了.它并不基于函数签名式,而是由有效表达式(valid expression)组成.再次看看doProcessing template 一开始的条件:
template <typename T>
void doProcessing(T& w) {
    if (w.size() > 10 && w != someNastyWidget) {
        ...
    }
}
    T(w的类型)的隐式接口看起来好像有这些约束:
    它必须提供一个名为size的成员函数,该函数返回一个整数值.
    它必须支持一个 operator!=函数,用来比较两个T对象.
    真的要感谢操作符重载(operator overloading)带来的可能性,这两个约束都不需要满足.是的,T必须支持size成员函数,然而这个函数也可能从base class 继承而得.这个成员函数不需要返回一个整数值,甚至需要返回一个数值类型.它唯一需要做的是返回一个类型为X的对象,而X对象加上一个int(10的类型)必须能够调用一个 operator>.这个 operator>不需要非得取得一个类型为X的参数不可,因为它可以取得类型Y的参数,只要存在一个隐式转换能够将类型X的对象转换为类型Y的对象!
    同样的道理,T并不需要支持 operator!=,因为一下这样也是可以的:operator!=接受一个类型为X的对象和一个类型为Y的对象,T可被转换为X而someNastyWidget的类型可被转换为Y,这样就可以有效地调用 operator!=.
    隐式接口是由一组有效表达式构成.
    注意:
    class 和 template 都支持接口(interface)和多态(polymorphism).
    对 class 而言接口是显式的(explicit),以函数签名为中心.多态则是通过 virtual 函数发生于运行期.
    对 template 参数而言,接口是隐式的(implicit),基于有效表达式.多态则是通过 template 具现化和函数重载解析(function overloading resolution)发生于编译期.


条款42:    了解 typename 的双重意义

Understand the two meaning of typename

    提一个问题:以下 template 声明式中,class 和 typename 有什么不同?
template <class T> class Widget;        // 使用class
template <typename T> class Widget;        // 使用typename
    答案:没有不同.当声明 template 类型参数,class 和 typename 的意义完全相同.
    然而C++并不总是把 class 和 typename 视为等价.有时候一定得使用 typename.为了了解其时机,必须先看看可以在 template 内指涉(refer to)的两种名称.
template <typename C>
void print2nd(const C& container) {
    if (container.size() > 2) {
        C::const_iterator iter(container.begin());
        ++iter;
        int value = *iter;
        std::cout << value;
    }
}
    在代码中特别强调两个local变量iter和value.iter的类型是C::const_iterator,实际上是什么必须取决于 template 参数C.template 内出现的名称如果相依于某个 template 参数,称之为从属名称.如果从属名称在 class 内呈嵌套状,称它为嵌套从属名称.C::const_iterator就是这样一个名称.
    另一个local变量 value,其类型是 int,int 是一个并不依赖任何 template 参数的名称,这样的名称是非从属名称.
    一般性规则很简单:任何时候当要在 template 中指涉一个嵌套从属类型名称,就必须在紧临它的前一个位置放上关键字 typename.
    注意:
    声明 template 参数时,前缀关键词 class 和 typename 可互换.
    请使用关键字 typename 标识嵌套从属类型名称:但不得在base class lists(基类列)或member initialization list(成员初始列)内以它作为base class 修饰符.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值