Effective C++ 笔记 第七部分 模板与泛型编程

41.了解隐式接口和编译期多态(Understand implicit interface and compile-time polymorphism.)


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

隐式接口:函数模板中出现的接口,此template必须对其编译有效。

template<typename T>
void doProcessing(T& w){
    if (w.size() > 10 ) {//size()和operator>是隐式接口
        T temp(w);       //copy构造函数是隐式接口
        temp.normalize();//normalize()是隐式接口
    }
}

编译期多态:以不同的template参数具现化function templates会导致调用不同的函数。

42.了解typename的双重意义(Understand the two meanings of typename.)


声明template参数时,前缀关键字class和typename可互换。
请使用关键字typename标识嵌套从属类型的名称;但不得在base class lists(基类列)或member initialization list内以他作为base class修饰符。

从属名称:template内出现的名称如果相依于某个template参数,称之为从属名称。如果从属名称在class内成嵌套状,称为嵌套从属名称。

template<typename T>
void doProcessing(T& w){
    if (w.size() > 10 ) {
        T temp(w);
        //const_iterator是一个嵌套从属名称
        typename T::const_iterator iter(w.begin());
        temp.normalize();
    }
}

typename有一个用途是被用来验证嵌套从属类型名称。

template<typename T>
void func(){
//下面这条语句有两种意义
//const_iterator是一个嵌套从属名称x是其类型的指针
//T中名为const_iterator的成员变量与x相乘
    T::const_iterator* x;
}
//修改为
template<typename T>
void func(){
//使用typename声明其位嵌套从属名称。
    typename T::const_iterator* x;
}

typename必须作为嵌套从属类型名称的前缀词这一规则的例外:
base class list中不允许typename。
member initialization list(成员初值列表)内不允许typename。

43.学习处理模板化基类内的名称(Know how to access names in templatized base classes.)


可在derived class templates内通过this->指涉base class templates内的成员名称,或籍由一个明白书写出”base class资格修饰符”完成。

假如我们需要写一个程序,能够将信息通过明文和密文传送到若干不同公司去。

class CompanyA{
public:
    void sendCleartext(const std::string& msg);//原文
    void sendEncrypted(const std::string& msg);//密文
};
class CompanyB{
public:
    void sendCleartext(const std::string& msg);//原文
    void sendEncrypted(const std::string& msg);//密文
};

//将MsgSender具现化为不同的公司以传递信息。
template<typename Company>
class MsgSender{
public:
    void sendClear(const std::string& info){
        std::string msg;
        //根据info设置msg;
        Company c;
        c.sendCleartext(msg);
    }
    void sendSecret(const std::string& info){
        //类似的实现
    }
};

现在我们希望每次送出信息时志记(log)某些信息。我们写一个dreived class来实现。

template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
    void sendClearMsg(const std::string& info){
        //传送前的信息写至"log"
        sendClear(info);//ERROR
        //传送后的信息写至"log"
    }
};

sendClear(info);语句将会无法通过编译,因为编译器遭遇class template LoggingMsgSender定义式时,并不知道他继承什么样的class,因为MsgSender< Company>的 Company不知道是什么。,也无法知道class MsgSender< Company>是否有个sendClear函数。例如:
若有一个公司Z只使用密文:


class CompanyZ{
public:
    void sendEncrypted(const std::string& msg);//密文
};

//对CompanyZ产生一个MsgSender特化版本
//template<>语法象征这既不是template也不是标准class
//而是特化版的MsgSender template
//在template实参是CompanyZ时使用。
template<>
class MsgSender<CompanyZ>{
public:
    void sendSecret(const std::string& info);
};

再看class LoggingMsgSender的sendClear(info);语句,如果Company = CompanyZ,那么这条语句将不存在。
C++拒绝sendClear(info);调用的原因是它知道base class templates有可能被特化,而那特化版本可能不提供和一般性template相同的接口,但是我们有三种方法告诉编译器sendClear(info);是可以调用的。

template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{

    //1.使用using声明告诉编译器,sendClear在base class内
    using MsgSender<Company>::sendClear;

    void sendClearMsg(const std::string& info){
        //2.使用this,指明sendClear被继承
        this->sendClear(info);

        //3.指明sendClear在base class内
        //但sendClear是virtual函数,会关闭virtual绑定行为
        MsgSender<Company>::sendClear(info);
    }
};

44.将与参数无关的代码抽离templates(Factor parameter-independent code out of templates.)


Templates生成多个classes和多个函数,所以任何template代码都不该与某个人造成膨胀的template参数产生相依关系。
因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参会或class成员变量替换template参数。
因类型参数而造成的代码膨胀,往往可以降低,做法是让带有完全相同二进制表述的具现类型共享实现码。

若我们为固定尺寸的正方矩阵编写一个template,实现求逆矩阵运算。

template<typename T,std::size_t n>
class SquareMatrix{ public: void invert(); };

上述这样的代码,在n不同时将会产生不同的class造成代码膨胀。我们应该将共性分离出来。

template<typename T>
class SquareMatrixBase{
public:
    SquareMatrixBase(std::size_t n,T* pMem);//n为矩阵大小pMem为矩阵指针
    void invert();
......
};

template<typename T,std::size_t n>
class SquareMatrix:private SquareMatrixBase<T>{
public:
    SquareMatrix():
    SquareMatrixBase<T>(n,data){}
    void invert(){
        SquareMatrixBase<T>::invert();
    }
private:
    T data[n*n];
};

45.运用成员函数模板接受所有兼容类型(Use member function templates to accept “all compatible types.”)


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

假如我们想实现智能指针,考虑到其copy构造函数若为普通构造函数写法,那么将具现化无限多的函数,因为其继承关系需要不同的copy构造函数来体现。解决方法是使用构造模板。这样的模板是所谓member function templates,简称member templates,其作用是为class生成函数。
在class内声明泛化的构造函数并不会阻止编译器生成他们自己的copy构造函数,若想控制“正常的”(非泛化)的copy构造函数,那么需要在声明一个。

template<typename T>
class SmartPtr{
public:
    //member templates,泛化copy构造函数
    template<typename U>
    SmartPtr(const SmartPtr<U>& other)
    :heldPtr( other.get() )//利用原始指针实现的继承性来确保智能指针的继承性
    {}

    //non-template,正常的copy构造函数
    SmartPtr(const SmartPtr<T>& other):
    heldPtr( other.get() )
    {}

    T* get() const{ return heldPtr; }
private:
    T* heldPtr;//原始指针
};

46.需要类型转换时请为模板定义非成员函数(Define non-member functions inside templates when type conversions are desired.)


当我们编写一个class template,而它所提供之”与此template相关的”函数支持”所有参数之隐式类型转换”时,请将那些函数定义为”class template内部的friend 函数”

将24的有理数相乘功能使用template实现

template<typename T>
class Rational{
public:
    //non-explicit的构造函数
    Rational(const T& numerator = 0,const T& denominator = 1):
    _numerator(numerator),
    _denominator(denominator)
    {}
    //getter函数
    const T numerator() const{
        return _numerator;
    }
    const T denominator() const{
        return _denominator;
    }
private:
    T _numerator;//分子
    T _denominator;//分母
};

template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs){
    return Rational<T>(lhs.numerator()*rhs.numerator(),
                    lhs.denominator()*rhs.denominator()
                    );

}
int main(int argc, const char * argv[]) {
    Rational<int> onHalf(1,2);

    //ERROR: Invalid operands to binary expression ('Rational<int>' and 'int')
    Rational<int> result = onHalf * 2;

     return 0;
}

然而发现Rational<int> result = onHalf * 2;语句出现错误。因为template实参推导过程中从不将隐式类型转换函数纳入考虑。所有编译器寻找这样的函数

const Rational<int> operator*(const Rational<int>& lhs,const int& rhs);

然而是不存在的,所有会报错。
解决方法是将operator声明为friend函数。因为当对象oneHalf被声明为一个Rational<int>,class Rational<int> 被具现化出来,而作为过程的一部分,friend函数operator*也就被自动声明出来。后者身为一个函数而非函数模板,因此编译器可以在调用他 时使用隐式转换函数。
但是仅仅这样缺无法通过链接,因为我们意图令此class外部的operator* template提供定义式,但是行不通——如果我们自己声明了一个函数,就有责任定义那个函数,既然我们没有提供定义式,那么链接器就找不到它。所以需要在class内提供其定义式。
由于在class声明内提供定义式,会被inline,所以较复杂的函数应该调用一个定义于class之外的辅助函数。

template<typename T>
class Rational;

//辅助函数
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs){
    return Rational<T>(lhs.numerator()*rhs.numerator(),
                       lhs.denominator()*rhs.denominator()
                       );
}
template<typename T>
class Rational{
public:
    //non-explicit的构造函数
    Rational(const T& numerator = 0,const T& denominator = 1):
    _numerator(numerator),
    _denominator(denominator)
    {}
    //声明为friend,并在类中定义
    //在class template内,template名称可用来作为template和其参数的简略表示
    //所以可以写Rational,不必写Rational<T>,不过写后者也ok
    friend
    const Rational operator*(const Rational& lhs,const Rational& rhs){
        return doMultiply(lhs, rhs);//调用辅助函数
    }
    //getter函数
    const T numerator() const{
        return _numerator;
    }
    const T denominator() const{
        return _denominator;
    }
private:
    T _numerator;//分子
    T _denominator;//分母
};

int main(int argc, const char * argv[]) {
    Rational<int> onHalf(1,2);
    Rational<int> result = onHalf * 2;

     return 0;
}

47.请使用traits classes表现类型信息(Use traits classes for information about types.)


Traits classes使得”类型相关信息”在编译期可用。他们以templates和”templates 特化”完成实现。
整合重载技术后,traits classes有可能在编译期对类型执行if…else测试。

traits classes是一种用来表现类型信息的技术。
例如STL中的advance函数,他的作用是给定迭代器和偏移位置,返回经过偏移后的迭代器。所以我们需要知道输入的迭代器类型信息,因为如果迭代器是随机访问迭代器,那么会有速度更快的算法用来选择。

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d){
    if (iter is radom access iterator ) {//需要迭代器类型来选择算法
        iter += d;
    }else{
        if (d > 0) {
                while (d--) {
                    ++iter;
            }else{
                while (d++) {
                    --iter;
                }
            }
        }
    }
}

下面以iterator_category作为例子解释traits技术
首先使用typedef关键字将类拥有的迭代器命名为iterator_category。
如deque使用随机访问迭代器random_access_iterator_tag,list使用bidirectional_iterator_tag

template<typename T>
class deque{
public:
    class iterator{
    public:
        typedef std::random_access_iterator_tag iterator_category;
    };
};
template<typename T>
class list{
public:
    class iterator{
    public:
        typedef std::bidirectional_iterator_tag iterator_category;
    };
};

iterator_traits则是相应iterator class的嵌套式typedef

template<typename IterT>
struct iterator_traits{
    typedef typename IterT::iterator_category iterator_category;
};

//针对指针迭代器的偏特化版本。指针与random_access_iterator_tag类似
template<typename IterT>
struct iterator_traits<IterT*>{
    typedef random_access_iterator_tag iterator_category;
};

现在可以实现advance的if内的语句iter is radom access iterator了

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d){
    if (typeid(typename std::iterator_traits<IterT>::iterator_category)
        == typeid(std::random_access_iterator_tag)
        ) {
    }
}

我们可以把if判定移到编译期来作,方法是使用函数的重载功能

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d){
    doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category());
}

template<typename IterT,typename DistT>
void doAdvance(IterT& iter,DistT d,std::random_access_iterator_tag);

template<typename IterT,typename DistT>
void doAdvance(IterT& iter,DistT d,std::bidirectional_iterator_tag);

//其他迭代器类型的重载函数

48.认识template元编程(Be aware of template metaprogramming.)


TMP可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。
TMP可被用来生成”基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

template meta programming(TMP,模板元编程)是编写template-base c++程序并执行于编译期的过程。

你可能感兴趣的:(C++)