《Effective C++》:条款41-条款42

  • 条款41了解隐式接口和编译期多态
  • 条款42了解typename的双重意义

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

面向对象编程总是以显示接口(explicit interfaces)和运行期多态(runtime polymorphism)来解决问题。例如

 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!=someNasyWidget)
        {
            Widget temp(w);
            temp.normalize();
            temp.swap(w);
        }
    }

可以这样说doProcessing内的w

  • w的类型被声明为Widget,所以w必须支持Widget接口。
  • Widget的某些成员函数是virtual,w对于这样函数的调用将表现出运行期多态(runtime polymorphism)。

在Templates和泛型编程的世界中,显示接口和运行期多态仍然存在,但是更要到的是隐式接口(implicit interface)和编译器多态(compile-time polymorphism)。将doProcessing从函数变为函数模板(function template)

template<typename T>
    void doProcessing(T& w)
    {
        if(w.size()>10 && w!=someNasyWidget)
        {
            Widget temp(w);
            temp.normalize();
            temp.swap(w);
        }
    }

现在再来看doProcessing内的w

  • w必须支持哪种接口,有template中执行于w身上的操作来决定。
  • 凡涉及w的任何函数调用,例如operator>和operator!=,有可能造成template的具现化(instantiated),使这些调用得以成功。这样的局现化发生在编译期。以不同template参数具现化function template会导致调用不同的函数,这就是编译期多态(compile-time polymorphism)。

编译期多态和运行期多态不难区分,现在来看显式接口和隐式接口的区别。通常显式接口有函数的签名式(函数名称、参数类型、返回值)构成。

    class Widget{
    public:
        Widget();
        virtual ~Widget();
        virtual std::size_t size() const;
        virtual void normalize();
        void swap(Widget& other);
        ……
    };

public接口由一个构造函数、一个析构函数、函数size,normalize、swap以及其参数、返回值、常量性(constness)构成,还包括编译器产生的copy构造函数和copy assignment操作符。

隐式接口就完全不同,它不是由函数签名决定,而是由有效表达式(valid expression)组成。

template<typename T>
    void doProcessing(T& w)
    {
        if(w.size()>10 && w!=someNasyWidget)
        {
            Widget temp(w);
            temp.normalize();
            temp.swap(w);
        }
    }

可以看出T(w类型)的隐式接口好像有这些约束

  • 必须提供一个名为size的函数,该函数返回一个整数值
  • 必须支持一个operator!=汗还是,用来比较两个对象。

其实并不是必须满足这两个约束。T必须支持size成员函数,但是这个函数可能从base class继承。这个函数不需要返回一个整数值,甚至不需要返回一个数值类型。甚至不需要返回一个定义有operator>的类型。它唯一要做的就是返回一个类型为X的对象,而X对象加上一个int(10的类型)必须能够调用一个operator>。这个operator>不需要非得取得一个类型为X的参数,它可以取得类型为Y的参数,只要存在一个隐式转换能够将类型X的对象转换为类型为Y的对象。同理T不需要支持operator!=。

以上分析还没有考虑operator&&被重载,一个连接词的改变或许完全不同的某种东西,可能改变上述表达式的意义。

第一次以此种方式思考隐式接口会感觉不习惯。隐式接口仅仅由一组有效表达式构成,这个表达式可能看起来很复杂,但它们要求的约束条件一般而言相当直接又明确,例如:

if(w.size()>10 && w!=someNasyWidget)

关于函数size、operator>、operator&&、operator!=身上的约束条件,很难再说太多;但整体确认表达式约束条件很容易。if表达式必须为布尔值,因此整体表达式必须与bool兼容。这时template doProcessing中类型参数T隐式接口的一部分,doProcessing要求其他隐式接口:copy构造函数、normalize和swap也必须对T型对象有效。

加诸于template参数身上的隐式接口和加诸于class对象身上接口一样真实,都是在编译期完成检查,如果template中使用不支持template所要求的隐式接口,代码不能编译通过

总结

  • class和template都支持接口和多态。
  • class的接口是显式的,以函数签名为中心,多态是通过virtual函数发生于运行期。
  • template的接口是隐式的,基于有效表达式。多态是通过template具体化和函数重载解析(function overloading resolution)发生在编译期。

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

使用模板时,可以用typename,也可以用class

 template<class T> class Widget;
    template<typename T> class Widget;

两者没有什么不同。作为template的类型参数,意义完全相同。在使用习惯上来说,很多人喜欢使用typename,因为这暗示参数并非一定要是个class类型。

C++有时不会把class和typename等价。有时候一定要用typename;为了了解原理,先来谈谈在template内指涉(refer to)的两种名称。

用一个例子来说明,现在有个template function,接收STL兼容容器为参数,容器内对象可被赋值为int,这个函数打印第二个元素的值

 template<typename C>
    void print2nd(const C& container)
    {
        if(container.size()>=2)
        {
            C::const_iterator iter(container.begin());
            ++iter;
            int valaue=*iter;
            std::cout<<value;
        }
    }

现在解释一下两个local变量iterate和value。iterate类型为C::const_iterator,实际是什么取悦于template参数C。template内出现的名称如果依赖于某个template参数,称这个名称为从属名称(dependent names)。如果从属名称在class内呈嵌套状,称之为嵌套从属名称(nested dependent name)。C::const_iterator就是这样的嵌套从属名称(nested dependent name),且指涉某类型。value是int类型,不依赖template参数,称之为非从属名称(non-dependent names)。

嵌套从属名称有可能导致解析(parsing)困难,例如在print2dn这样做

template<typename C>
void print2nd(const C& container)
{
    C::const_iterator* x;
    ……
}

表面上看是声明了一个local变量x,x类型是只需C::const_iterator的指针,这是因为我们直到C::const_iterator是个类型。如果不是这样呢?例如,C内有个static成员变量碰巧命名为const_iterator,或x是个global变量;这样的话就是一个相乘动作:const::const_iterator乘以x。编写C++解析器的人必须操心这样的输入。

在直到C是什么之前,不能确定C::cont_iterator是否为一个类型。当编译器解析template print2nd时,不知道C是什么东西。C++有个规则可以解析这个歧义状态:如果解析器在template中遭遇到一个嵌套从属名称,它便假设这个明白不是个类型,除非你告诉它是。所以缺省情况下,嵌套从属名称不是类型。这个规则还有个例外,稍后再提。

再来看看print2nd,C::const_iterator iter只有在C::const_iterator是个类型时才合理,但是C++缺省认为它不是。要矫正这个形式,我们必须告诉C++C::const_iterator是个类型,在其前面放置关键字typename即可

 template<typename C>
    void print2nd(const C& container)
    {
        if(container.size()>=2)
        {
            typename C::const_iterator iter(container.begin());
            ++iter;
            int valaue=*iter;
            std::cout<<value;
        }
    }

“typename必须作为嵌套从属类型名称的前缀词”这一规则的例外是:typename不可以出现在base class list内的嵌套从属类型名称之前,也不可以在member initialization list(成员初值列)中作为base class修饰符。

 template<typename T>
    class Derived: public Base<T>::Nested{ //base class list中不允许typename
    public:
        explicit Derived(int x)
        :Base<T>::Nested(x)  //mem init.list中不允许typename
        {
            typename Base<T>::Nested temp; //嵌套从属名称类型
            ……  //既不在base class list中,也不再mem init list中,作为base class修饰符要加上typename
        }
        ……
    };

再来看一个typename例子。一个function template接受一个迭代器,我们打算为该迭代器指涉的对象做一份local复件(副本)temp

    template<typename IterT>
    void workWithIterator(IterT iter)
    {
        typename::std::iterator_traits<IterT>::value_type temp(*iter);
        ……
    }

typename::std::iterator_traits<IterT>::value_type是标准traits class(**条款**47)的一种运用。std::iterator_traits<IterT>::value_type是个嵌套从属类名称,所以在其前面放置typename。如果使用std::iterator_traits<IterT>::value_type感觉太长不习惯,可以考虑建立一个typedef,对于traits成员名称,普遍的习惯是设定typedef名称代表某个traits成员名称。

 template<typename IterT>
    void workWithIterator(IterT iter)
    {
        typedef typename::std::iterator_traits<IterT>::value_type value_type;
        value_type temp(*iter);
        ……
    }

最后提一下,typename相关规则在不同编译器上有不同的实践。这意味着typename和嵌套从属名称之间的互动,也许会在移植性方面带来头疼的问题。

总结

  • 声明template参数时,前缀关键字class和typename可以互换。
  • 使用关键字typename标识嵌套从属类型名称;但不得在base class list(基类列)或member initialization list(成员初值列)内以它作为base class修饰符。
时间: 2024-10-14 20:43:12

《Effective C++》:条款41-条款42的相关文章

EC读书笔记系列之17:条款41、42、43、44、45、46

条款41 了解隐式接口与编译器多态 记住: ★classes和templates都支持接口和多态 ★对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtual函数发生于运行期 ★对templates而言,接口是隐式的(implicit),奠基于有效表达式.多态则是通过template具现化和函数重载解析发生于编译期 条款42 了解typename的双重意义 记住: ★声明template参数时,前缀关键字class和typename可互换(函数模板或类模板

《Effective C++》:条款35:考虑virtual函数以外的其他选择

条款35:考虑virtual函数以外的其他选择 条款35考虑virtual函数以外的其他选择 藉由Non-virtual Interface手法实现Template Method模式 藉由Function Pointers实现Strategy模式 藉由tr1function完成Strategy模式 古典的Strategy模式 摘要 virtual函数在派生中经常用到,在遇到一些问题时用virtual函数没问题,但是有时候我们应该思考一下是否有替代方案,以此来拓宽我们的视野. 假如现在正在写一个游

《Effective C++》:条款48:理解力template 元编程

Template metaprogramming(TMP,模板元编程)这是写template-based C++规划.编译过程.template metaprogramming随着C++写模板程序,化的过程.也就是说,TMP程序运行后,从templates详细化出来C++源代码.不再是模板了. TMP有两个作用,一是它让某些事更easy. 比如编写STL容器,使用模板,可是存放不论什么类型元素.二是将运行在运行期的某些工作转移到了编译期.另一个结果是使用TMP的C++程序可能在其它方面更高效:较

Effective C++ 55个条款

Effective C++ 55个条款 让自己习惯C++ 视C++为一个语言联邦 C++高效编程守则视状况而变化,取决于你使用C++的哪一部分. 尽量以const,enums,inline替换#define 对于单纯常量,最好以const对象或enums替换#defines: 对于形似函数的宏(macros),最好改用inline函数替换#defines. 尽可能使用const 将某些东西声明为const可帮助编译器侦测出错误用法.const可被施加于任何作用域内的对象.函数参数.函数返回类型.

Effective C++ Item 41 了解隐式接口和编译期多态

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:class 和 templates 都支持接口和多态. 对 classes 而言接口是显式的,以函数签名为中心.多态则是通过 virtual 函数发生于运行期 对 templates 参数而言,接口是隐式的,奠基于有效表达式.多态则是通过 templates 具体化和函数重载解析(function overloading resolution)发生于编译期. 示例1:显式接口和运行期

Effective C++ 条款41

本节条款对比了一下两对概念的对比. 首先是 编译期多态和运行期多态. 接着是 显示接口和隐式接口 编译期多态是由于模板而产生的. 如下代码: #include<iostream> using namespace std; class Bird { public: Bird(int v):value(v){} int getSize(){ return value; } private: int value; }; class Person//鸟 { public: Person(int v)

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

classes和templates都支持接口(interface)和多态(polymorphism). 对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtual函数发生于运行期. 对template参数而言,接口是隐式的(implicit),奠基于有效表达式.多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期.

Effective C++ 条款41 了解隐式接口和编译期多态

1. 面向对象编程通常以显式接口(类中的函数原型)和运行时多态(虚函数和RTTI)解决问题,但在Templates及泛型编程的世界,尽管显式接口和运行时多态仍然存在,但隐式接口和编译时多态的重要性却明显提升. 2.通常显式接口由函数签名式构成,而隐式借口并不基于函数声明式,而是有有效表达式组成. 所谓泛型编程中的隐式接口,指的是类型参数在函数模板或类模板中的成员函数内部所涉及的操作.也就是说类型参数必须支持函数模板内部所进行的操作,比如operator>,operator!=等,而对于不同类型,

《Effective C++》之条款32:确定你的public继承塑模出is-a关系

<Effective C++> 条款32:确定你的public继承塑模出is-a关系 Public inheritance(公开继承)意味"is-a"的关系. 例子如下: class Person{ ... }; class Student : public Person{ ... }; void eat(const Person& p);//任何人都会吃 void study(const Student& s);//只有学生才到校学习 Person p;/