《Effective C++》:条款44-条款45

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

Templates可以节省时间和避免代码重复。对于类似的classes或functions,可以写一个class template或function template,让编译器来做剩余的事。这样做,有时候会导致代码膨胀(code bloat):其二进制码带着重复(或几乎重复)的代码、数据,或者两者。但这时候源代码看起来可能很整齐。

先来学习一个名词:共性与变性分析(commonality and variability analysis)。比较容易理解。例如,你在编写几个函数,会用到相同作用的代码;这时候你往往将相同代码搬到一个新函数中,给其他几个函数调用。同理,如果编写某个class,其中某些部分和另外几个class相同,这时候你不会重复编写这些相同部分,只需把共同部分搬到新class中去即可,去使用继承或复合(**条款**32,38,39),让原先的classes取用这些共同特性,原classes的互异部分(变异部分)仍然留在原位置不动。

编写templates时,也要做相同分析,避免重复。non-template代码中重复十分明确:你可以看到两个函数或classes之间有所重复。但是在template代码中,重复是隐晦的,因为只有一份template源码。

例如,你打算在为尺寸固定的正方矩阵编写一个template,该矩阵有个支持逆矩阵运算的函数

 template<typename T, std::size_t n>//T为数据类型,n为矩阵大小
    class SquareMatrix{
    public:
        ……
        void invert();//求逆运算
    };
    SquareMatrix<double,5> sm1;
    sm1.invert();//调用SquareMatrix<double,5>::invert
    SquareMatrix<double,10> sm2;
    sm2.invert();//调用SquareMatrix<double,10>::invert

上面会具体化两份invert。这两份函数几乎完全相同(除了一个操作5*5矩阵,一个操作10*10)。这就是代码膨胀的一个典型例子。

上面两个函数除了操作矩阵大小不同外,其他相同。这时可以为其建立一个带数值的函数,而不是重复代码。于是有了对SquareMatrix的第一份修改:

 template<typename T>
    class SquareMatrixBase{
    protected:
        void invert(std::size_t matrixSize);
        ……
    };
    template<typename T, std::size_t n>
    class SquareMatrix:private SquareMatrixBase<T>{
    private:
        using SquareMatrixBase<T>::invert();//编码遮掩base中的invert,**条款**33
    public:
    ……
        void invert()//求逆运算
            {
                this->invsert(n);//稍后解释为什么用this
            }
    };

SquareMatrixBase::invert只是企图避免derived classes代码重复,所以它以protected替换public。这个函数使用this->,因为模板化基类内的函数名称会被derived classes掩盖(条款**43)。注意,SquareMatrixBase和SquareMatrix之间继承关系是private,这说明base class是为了帮助derived classes实现,两者不是**is-a关系。

现在还有一个问题,SquareMatrixBase::invert操作的数据在哪?它在参数中直到矩阵大小,但是矩阵数据derived class才知道。derived class和base class如何联络?一个做法是可以为SquareMatrixBase::invert添加一个参数(例如一个指针)。这个行得通,但是考虑到其他因素(例如,SquareMatrixBase内还有其他函数,也要操作这些数据),可以把这个指针添加到SquareMatrixBase类中。

    template<typename T>
    class SquareMatrixBase{
    protected:
        SquareMatirxBase(std::size_t n,T* pMem)
        :size(n), pData(pMem){}
        void setDataPtr(T* ptr) {pData=ptr;}
        ……
    private:
        std::size_t size;
        T* pData;
    };
    template<typename T, std::size_t n>
    class SquareMatrix:private SquareMatrixBase<T>{
    public:
        SquareMatrix()
        :SquareMatrixBase<T>(n, data){}
        ……
    private:
        T data[n*n];
    };

这种类型的对象不需要动态分配内存,但是对象自身可能非常大。另一个做法是把矩阵数据放到heap

    template<typename T, std::size_t n>
    class SquareMatrix:private SquareMatrixBase<T>{
    public:
        SquareMatrix()
        :SquareMatrixBase<T>(n, 0),
        pData(new T[n*n])
        {this->setDataPtr(pData.get());}
        ……
    private:
        boost::scoped_array<T> pData;
    };

这样以来,类型相同的derived classes会共享base class。例如,SquareMatrix

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

在模板中,具体化模板参数后的类不会因为具体化类型而存在派生关系。来看一个关于指针的例子。真实指针支持隐式转换(implitic conversions);derived class指针可以隐式转换为base class指针,指向non-const对象的指针可以转换为指向const对象的指针,等等。例如:

    class Top{……};
    class Middle: public Top{……};
    class Bottom:public Middle{……};
    Top* pt1=new Middle;//Middle* 转换为Top*
    Top* pt2=new Bottom;//Bottom* 转换为Top*
    const Top* pct2=pt1;//Top* 转换为const Top*

如果使用模板定义智能指针,上面的转换就有点麻烦了

    template<typename T>
    class SmartPrt{
    public:
        explicit SmartPtr(T* realPtr);
        ……
    };
    SmartPtr<Top> pt1=SmartPtr<Middle>(new Middle);//SmartPtr<Middle>转换为SmartPtr<Top>
    SmartPrt<Top> pt2=SmartPrt<Bottom>(new Bottom);
    SmartPrt<const Top> pct2=pt1;

同一个template的不同具体化之间不存在什么关系,即使具体化的两个类型之间有继承、派生关系。编译器把SmartPtr和SmartPtr视为完全不同两种类型的classes。为了让上面代码编译通过,获得SmartPtr classes之间的转换能力,必须明确的把它们编写出来。

Templates和泛型编程

要想实现转换,可以在智能指针的构造函数中完成,但是如果派生类有继续派生,那么构造函数又要添加,这显然不合理。因此,我们需要的不是简简单单为SmartPtr写构造函数,而是编一个构造模板。这么的模板是所谓的member function template(简称member templates),作用是为class生成函数

    template<typename T>
    class SmartPrt{
    public:
        template<typename U>
        SmartPtr(const SmartPrt<U>& other);//member template,为了生成copy cotr
        ……
    };

以上代码意思是,对任何类型T和任何类型U,可以根据SmartPrt生成一个SmartPtr。copy cotr没有声明为explicit,因为转换可能是隐式的。

这个为SmartPtr而写的泛化构造函数提供的东西比我们需要的更多。我们希望根据一个SmartPtr创建一个Smartprt,却不希望根据一个SmartPtr创建一个SmartPtr,因为对于public继承来说是矛盾的。

上述代码并不完整,在SmartPtr没有实现copy cotr。假设SmartPtr像auto_ptr和tr1::shared_ptr一样,提供get成员函数,返回智能指针对象,那么就可以在构造模板中约束转换行为

    template<typaname T>
    class SmartPtr{
    public:
        template<typename U>
        SmartPrt(const SmartPrt<U>& other)
        :heldPrt(other.get()){};
        T* get() const{return heldPrt;}
        ……
    private:
        T* heldPrt;
    };

在上述代码中,存一个隐式转换:将U* 转换为 T*,这限制了转换行为。

member function templates作用不仅仅在于构造函数,还有一个重要作用是支持赋值操作。例如TR1的shared_ptr支持所有来自兼容之内置指针、tr1::shared_ptrs、auto_ptrs和tr1::weak_ptrs的构造行为,以及来自上述各物(tr1::weak_ptr除外)的赋值操作。来看一下TR1规范中关于tr1::shared_ptr的一份摘录

    template<class T>
    class shared_ptr{
    public:
        template<class Y>
            explicit shared_ptr(Y* p);
        template<class Y>
            shared_ptr(shared_ptr<Y> const& r);
        template<class Y>
            explicit shared_ptr(weak_ptr<Y> const& r);
        template<class Y>
            explicit shared_ptr(auto_ptr<Y> const& r);
        template<class Y>
            shared_ptr& operator=(shared_ptr<Y> const& r);
        template<class Y>
            shared_ptr& operator=(auto_ptr<Y> const& r);
        ……
    };

上面除了泛化copy构造函数外,其他构造函数都是explicit,表示shared_ptr类型隐式转换被允许,但是从其他智能指针隐式转换为shared_ptr不被允许。

member function templates并不改变语言基本规则,和编译器产生copy构造函数以及copy assignment不冲突。tr1:shared_ptr声明了一个泛化的copy构造函数,如果T和Y相同,泛化的copy构造函数会被具体化为正常的copy构造函数。编译器会暗自为tr1::shared_ptr生成一个copy构造函数?还是当tr1::shared_ptr对象根据另一个同类型的tr1::shared_ptr对象展开构造行为时,编译器会将泛化的copy构造函数模板具体化呢?

member templates没有改变语言规则,如果程序需要一个copy构造函数,你却没有声明它,编译器就会替你生成。在class内声明泛化copy构造函数并不阻止编译器生成它们自己的copy构造函数(non-template)。如果想要控制copy构造函数的方方面面,就要声明正常的copy构造函数。相同的规则也适用于赋值assignment操作。

总结
- 请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数。
- 如果声明member templates用于泛化copy构造函数或泛化assignment操作,还是要声明正常的copy构造函数和copy assignment操作符。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值