Effective C++学习第十一天

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

        面向对象编程世界总是以显式接口(源码可见的接口)和运行期多态(virtual)解决问题;对于templates及泛型编程的世界,隐式接口编译期多态显得更加重要;

        对于template,只有当参数具体确定时(具现化)才能确定具体操作,而这些具现化的行为发生在编译期,以不同的template参数具现化function templates会导致调用不同的函数,这就是所谓的编译期多态

         通常显示接口由函数的签名式(函数名称,参数类型,返回类型)构成,而隐式接口则不基于函数签名式,它是由有效表达式组成,表达式自身看起来很复杂,但它们要求的约束条件一般而言直接而又明确;

        template参数身上的隐式接口,和class对象上的显示接口,都是在编译器完成检查;

        对template参数而言,接口是隐式的,基于有效表达式;多态则是通过template具现化和函数重载解析发生于编译器

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

          在声明template类型参数时,class和typename意义完全相同;

          引申:template内出现的名称如果相依于某个template参数,称之为从属名称,如果从属名称在class内呈嵌套状,我们称它为嵌套从属名称,如c::const_iterator就是这样的名称,它其实是一个嵌套从属类型名称,也就是个嵌套从属名称并指涉某类型;如果一个名称(int)并不依赖于template参数的名称,这样的名称称为谓非从属名称;代码如下:

template<typename c>

void print2nd(const c&container)

{

           if(container.size()>=2){

             c::const_iterator iter(container.begin( ));

              ++iter;

              int value=*iter;

           }

}

        嵌套从属名称会导致编译困难,如c::const_iterator *p;C++解析代码有一个规定:如果解析器在template中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,除非你告诉它是,所以缺省情况下,嵌套从属名称不是类型(有一个例外);正确的情况就是告诉C++解析器这个一个类型,在名称前加关键字typename就可以了;

       typename只被用来验明嵌套从属类型名称,其他名称不该有它的存在;typename作为嵌套从属类型名称的前缀词有一个例外:typename不可以出现在base classes list内的嵌套从属结构,也不可在member initialization list(成员初始列表)中作为base class修饰符,如:

template <typename T>

class derived:public base<T>::Nested{

public:

        explicit derived(int x):base<T>::Nested(x){

          typename base<T>::Nested temp;

        }

};

条款43:学习处理模块化基类内的名称

              考虑如下的程序代码:

template <typename company>                           template<typename company>

class msgsender{                                                    class loggingmsgsender:public msgsender<company>{

public:                                                                  public:

       void sendclear(const msginfo & info)                void sendclearmsg(const msginfo & info){

      {

          std::string msg;                                                    sendclear(info);//调用base class函数,代码编译不通过

         company c;

        c.sendcleartext(msg);                                         }

    }                                                                            };

};

      分析:当编译器遇到class template loggingmsgsender定义式,并不知道它继承什么样的class,当然它继承的是msgsender<company>,但其中的company是个template参数,不到后来(loggingmsgsender被具现化)无法确切知道它是什么,也就不知道是否有sendclear这个函数;C++编译器拒绝调用sendclear的原因:它知道base classtemplates有可能被特化,而那个特化的版本不提供和一般性template相同的接口

      解决C++不进入templatized base classes的方法有三种:1)在base class函数调用之前加上this->,也就是this->sendclear(info);2)使用using声明式,告诉编译器sendclear在base class内,如using msgsender<company>::sendclear;3)明确指出被调用函数位于base class内,如msgsender<company>::sendclear(info);但是如果调用的是virtual函数,那么就会关闭virtual绑定行为;

条款44:将与参数无关的代码抽离templates

         templates是节省时间和避免代码重复的一个奇方妙法,但是使用templates可能会导致代码膨胀,其二进制码带着重复的代码数据;可以通过使用共性和变性分析的方法解决这个问题,将多个class的共同部分搬离到新的class,并通过继承或复合的方式来得到公共属性;

         任何templates代码都不该与某个造成膨胀的templates参数产生相依的关系;

         因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换templates参数,如矩阵类求逆实现;

         因类型参数而造成的代码膨胀,往往可以降低,做法是让带有完全相同二进制表述的具现类型共享实现代码,如int和long实现;

       如果你实现某些成员函数而他们操作强型指针(如T*),你应该令他们调用另一个操作无类型指针(void *)的函数,后者完成实际工作;

条款45:运用成员函数模板接受所有兼容类型

           真实指针支持隐式转换,如derived class隐式转换成base class;但是同一个templates的不同具现体之间并不存在什么与生俱来的固有关系(如某个带有base-derived关系的B,D两类型分别具现某个template,产生的两个具现体并不带有base-derived关系);

           C++给我们提供一个称为member function templates,它为模板类提供构造函数;这一类构造函数通过对象u创建对象t,而u和t的类型是同一个template的不同具现体,我们称这个函数为泛化构造函数,如:

template<typename T>

class smartptr{

public:

       template<typename u>

      smartptr(const smartptr<u>& other);

};

         但是对于泛化构造函数,有时我们需要指定隐式转换的方向,例如我们不能将int *转换为double*,这时候我们可能需要一个指针指向class的原始数据,这样问题就变成只有存在两个底层指针可以转换的才是我们需要的结果;

        member function templates(成员函数模板)的效果不限于构造函数,它还支持赋值操作,如shared_ptr类的代码:

template<typename T>

class shared_ptr{

public:

    template<class Y>

     explicit shared_ptr(Y* p);//没有const,原因???

     template<class Y>

     shared_ptr(shared_ptr<Y>const&r);//没有explicit,允许隐式转换

     template<class Y>

     explicit shared_ptr(weak_ptr<Y>const&r);

    template<class Y>

     explicit shared_ptr(auto_ptr<Y>&r);//没有const,auto_ptr性质决定

    template<class Y>

     shared_ptr&operator=(shared_ptr<Y>const &r);

    template<class Y>

     shared_ptr&operator=(auto_ptr<Y>&r);//没有const,auto_ptr性质决定

};

     member function templates会声明一个泛化的copy构造函数和copy assignment操作符,但是它不会改变C++语言的规则,C++语言规定如果程序需要一个copy构造函数,你没有声明,编译器会自动帮你生成一个,相同规则也适用于赋值操作,因此如果你不想让系统声明正常的拷贝构造函数和赋值操作符,你得自己声明;

条款46:需要类型转换时请为模板定义非成员函数

         当不存在模板时,如果函数参数都存在隐式转换,则需要将这个函数定义成非成员函数;当存在模板时,这条规则又发生了变化,编译无法通过,因为在template具现化实参推导过程中从不将隐式类型转换函数纳入考虑;

        解决的方法:利用friend关键字,将非成员函数声明为友元函数(新用法),同时提供实现(实现在函数外,通过友元函数调用,这样可以实现代码冲击最小化)(如果只是单纯提供友元声明,那么可以通过编译,却没有办法完成连接,这种称为混合式代码调用,代码调用成功的原因是:函数在被调用的过程中参数可以实现隐式变换;

       类模板的声明式为:template<typename T> class rational;     

猜你喜欢

转载自blog.csdn.net/xx18030637774/article/details/80918334