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

条款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修饰符。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值