[读书笔记][effective C++]条款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 的子类。
  • Widget的某些成员函数是virtual,w对于这样函数的调用将表现出运行期多态(runtime polymorphism)。

在模板编程的世界中,显示接口和运行期多态仍然存在,但是更要到的是隐式接口(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应该支持哪些方法)

凡涉及w的任何函数调用,例如 operator>和operator!=,有可能造成template的具现化(instantiated),使这些调用得以成功。这样的局现化发生在编译期。以不同template参数具现化 function template会导致调用不同的函数,这就是编译期多态(compile-time polymorphism)。

通常显式接口有函数的签名式(函数名称、参数类型、返回值)构成。
public接口由一个构造函数、一个析构函数、函数size,normalize、swap以及其参数、返回值、常量性(constness)构成,还包括编译器产生的copy构造函数和copy assignment操作符。
再看一遍这个代码

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

隐式接口和面向对象编程完全不同,它不是由函数签名决定,而是由有效表达式(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>。 (类似于重载运算符 bool operator>(X, int );)

这个operator>不需要非得取得一个类型为X的参数,它可以取得类型为Y的参数,只要存在一个隐式转换能够将类型X的对象转换为类型为Y的对象。
同理T不需要支持operator!=,只要在调用这个方法的命名空间,有重载运算符 bool operator!=(T, someNasyWidget的类型 ) 即可。
以上分析还没有考虑operator&&被重载,一个连接词的改变或许完全不同的某种东西,可能改变上述表达式的意义。

第一次以此种方式思考隐式接口会感觉不习惯。隐式接口仅仅由一组有效表达式构成,这个表达式可能看起来很复杂,但它们要求的约束条件一般而言相当直接又明确,例如:
if(w.size()>10 && w!=someNasyWidget)
关于函数size、operator>、operator&&、operator!=身上的约束条件,很难再说太多;但整体确认表达式约束条件很容易。if表达式必须为布尔值,因此整体表达式必须与bool兼容。这是template doProcessing中类型参数T隐式接口的一部分,doProcessing要求其他隐式接口:

  • copy构造函数(更准确的说是,Widget必须有一个构造函数,接受T类型的参数,否则就会编译失败 )
  • Wight的swap也必须对T型对象有效。

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

所以,所以我们可以得到一个结论, C++ 模板编程过于灵活,既支持面向过程编程,又支持操作符重载,给程序带来了极大的灵活性,同时也是不确定性,如果不仔细考虑,可能导致程序执行出现未定义行为的情况,所以我们要遵守一些开发规范,来约束自己的编程习惯,从而降低这些事的可能性。

当然,虽然概率比较低,但是我们应该严格要求自己。

最后总结

面向对象编程(特指以继承为核心)和template都支持接口和多态。
面向对象编程的接口是显式的,以函数签名为中心,面向对象的多态是通过virtual函数发生于运行期。
template的接口是隐式的,基于有效表达式。模板的多态是通过template具体化和函数重载解析(function overloading resolution)发生在编译期。其中表达式运算符的解读的多种可能 给模板编程带来了极大的不确定性!!!

还有最后一个问题,模板函数的重载如何考量? 模板函数能不能同名同重载的问题?之所以这么问,是因为对于模板而言,类型是不确定的。
答案是:首先,模板函数的出现,本身就为了消除多类型的重载,所以模板函数不存在针对类型的重载,所谓的模板函数重载,只可能针对函数数量进行重载。

参考

[1]KangRoger的 ec 读书笔记
https://blog.csdn.net/kangroger/category_2771821.html

[2]《Effective C++》:条款42 模板编程的隐式接口
https://blog.csdn.net/KangRoger/article/details/44182087

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值