Effective C++笔记(9)—模板与泛型编程(一)

来源:互联网 发布:编程思维 编辑:程序博客网 时间:2024/05/29 07:41

模板内容很丰富,分两次记录吧


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

对应了显示接口和运行期多态。
下面一个例子说明:

void func(Widget &w){    cout<<w.size()<<endl;}

func参数类型被声明为Widget,所以w必须支持Widget接口。而Widget类中包含的接口可以在声明Widget的文件中(Widget.h)找到,因此被称为显示接口。Widget的接口可能是一个虚函数或者纯虚函数(取决于子类是否继承父类的默认版本),在运行期将根据w的动态类型决定究竟调用哪一个版本。

模板编程引入了编译器多态和隐式接口的概念,看下面例子:

template <class T>void func(T &t){    cout << t.size() << endl;}

该模板函数要求T必须支持size接口,否则在编译器具现化的时候就会报错,比如说:

template <class T>void func(T &t){    cout << t.size() << endl;}class Foo{};int main(){//  int a = 5;//  func<int>(a);//编译期报错C2228 size的左边需要为class/struct/union类型//  Foo foo;//  func<Foo>(foo);//编译期报错C2039  size不是Foo的成员    string s = "hello";    func(s);//ok 输出5    system("pause");    return 0;}

上面的例子可以看出,传入模板实参需要支持模板函数中所支持的隐式接口,否则将在编译期报错。上述例子中除了string,还可以是vector等STL容器包含size接口,在编译器具现化出不同的func版本,便是所谓的编译期多态。

简单的理解:
编译器多态:哪一个重载函数该被调用
运行期多态:哪一个虚函数将被绑定

当然除此之外,隐式接口需要基于有效的表达式,比如上述例子中,如果某个结构体中包含一个接口size(),但返回类型是一个void,那么在编译器也会有问题,因为<<操作符不接受void类型的右操作数(error C2679)


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

在模板声明的时候可以这样:

template <class T> class Widget;template <typename T> class Widget;

使用class和typename都是ok的,但是这里要介绍typename另外一个class无法替代的作用。

举个例子:

template <class C>void print2nd(const C& container){    if (container.size() >= 2)    {        C::const_iterator iter(container.begin());        ++iter;        C::value_type value= *iter;        cout << value << endl;    }}//打印容器的第二个元素

这里用到了两个嵌套从属类型:
C::const_iterator
C::value_type
这里可能有歧义,虽然我们知道在STL容器中进行了typedef。但C::const_iterator或者C::value_type可能是类型C里面的static成员变量,这样就会造成歧义。合法的C++代码应该加上typename关键字明确这是一个类型名:

template <class C>void print2nd(const C& container){    if (container.size() >= 2)    {        typename C::const_iterator iter(container.begin());        ++iter;        typename C::value_type value= *iter;        cout << value << endl;    }}

在嵌套从属类型名前需要加上typename,除了以下情况:

template<class T>class Derived :public Base<T>::Nested{//1.不允许出现在Base Class Listpublicexplicit Derived(int x): Base<T>::Nested(x){}//2.不允许出现在成员初始化列表中。    {        typename Base<T>::Nested tmp;//非上述两种情况需要加typename    }};

另外书中给出了最后一个例子,这个例子看过STL源码剖析的应该不陌生:

template <typename iterT>void func(iterT iter){    typename std::iterator_traits<iterT>::value_type t(*iter);}

这里用迭代器萃取器获得迭代器所指对象的类型,做为临时变量t的类型。
结合书中的两个例子,上述print2nd也可以这样写:

template <class C>void print2nd(const C& container){    if (container.size() >= 2)    {        typename C::const_iterator iter(container.begin());        ++iter;        typedef typename C::const_iterator  const_iterator;        typedef typename std::iterator_traits<const_iterator>::value_type value_type;        value_type value(*iter);        cout << value << endl;    }}

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

先来看书上的例子:

class CompanyA{public:    void sendCleartext(const string&msg)    {        cout << "A::sendCleartext" << endl << msg << endl;    }    void sendEncrypted(const string&msg)    {        cout << "A::sendEncrypted" << endl << msg << endl;    }};class CompanyB{public:    void sendCleartext(const string&msg)    {        cout << "B::sendCleartext" << endl << msg << endl;    }    void sendEncrypted(const string&msg)    {        cout << "B::sendEncrypted" << endl << msg << endl;    }};template <class Company>class MsgSender{public:    void sendClear(const string &msg)    {        Company c;        c.sendCleartext(msg);    }};int main(){    //CompanyA a;    MsgSender<CompanyA> sa;    sa.sendClear("zhangxiao");//调用A的版本    MsgSender<CompanyB> sb;    sb.sendClear("zhangxiao2");//调用B的版本    system("pause");    return 0;}

这是template的解法,当然完全可以用一个Company接口类,然后A和B分别继承这个抽象类,然后在实现虚方法。
不过本条款要说的核心是,模板基类的问题:

class CompanyZ{public:    void sendEncrypted(const string&msg)    {        cout << "Z::sendEncrypted" << endl << msg << endl;    }};template <>class MsgSender < CompanyZ >{public:    void sendClear(const string &msg)    {        CompanyZ cz;        cz.sendCleartext(msg);    }};template <class Company>class LoggingMsgSender :public MsgSender<Company>{public:    void sendClearMsg(const string &msg)    {    //在vs2013并没有出现作者说的编译错误问题!!        sendClear(msg);    }};int main(){    LoggingMsgSender<CompanyA> lms;    lms.sendClearMsg("zhangxiao");    system("pause");    return 0;}

在之前的基础上,加入了一个CompanyZ的特化版本,并用一个派生类继承了MsgSender。

在vs2013并没有出现作者说的编译错误问题!!

不过可能这是编译器相关,我并没有在gcc下尝试编译,不过作者用了一个词语:对严守规律的编译器而言,无法通过

作者在书中提到,在LoggingMsgSender具现化之前,并不能知道它继承的东西是何物,也就是不知道class MsgSender<Company>看起来像什么,就更不用说知道有个sendClear函数了

更加严谨的做法有三种:

//1.template <class Company>class LoggingMsgSender :public MsgSender<Company>{public:    void sendClearMsg(const string &msg)    {        this->sendClear(msg);    }};//2.template <class Company>class LoggingMsgSender :public MsgSender<Company>{public:    using MsgSender<Company>::sendClear;    void sendClearMsg(const string &msg)    {        sendClear(msg);    }};//3.template <class Company>class LoggingMsgSender :public MsgSender<Company>{public:    void sendClearMsg(const string &msg)    {        MsgSender<Company>::sendClear(msg);    }};

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

两个函数都要调用相同的代码,我们会把相同的的代码放到一个函数中。
两个类中的某些代码重复,你会把共同的部分放到一个新class里面去,然后使用继承、复合等手段重复利用。

编写模板时,我们也希望达到相同的效果,但这并不容易,因为在具现化之前,我们需要预测一下哪些部分是重复的。先看书中的例子:

template <class T,std::size_t n>class SquareMatrix{public:    void invert()    {        cout << "invert" << " " << n << endl;    }};int main(){    SquareMatrix<int, 5>m1;    SquareMatrix<int, 10>m2;    m1.invert();// invert 5    m2.invert();//invert 10    system("pause");    return 0;}

上述代码通过非类型参数将模板类具象化出两个版本的invert分别表示计算5×510×10的方阵的逆矩阵。

这样template就造成了代码膨胀了,对其进行第一次修改:

template <class T>class SquareMatrixBase{protected:    void invert(std::size_t n)    {        cout << "invert" << " " << n << endl;    }};template<class T,std::size_t n>class SquareMatrix :private SquareMatrixBase < T >{public:    void invert()    {        SquareMatrixBase<T>::invert(n);    }};int main(){    SquareMatrix<int, 5>m1;    SquareMatrix<int, 10>m2;    m1.invert();// invert 5    m2.invert();//invert 10    system("pause");    return 0;}

这样不同尺寸大小的矩阵只有一个版本的Invert。用一个模板基类去做处理,最后书中给出了这个例子的终极版本,就是解决如何将矩阵数据传给基类:
书中用了boost::scoped_array这是一个管理动态数组的智能指针,我的VS下面没有装boost,就用shared_ptr/unique_ptr来代替了(https://stackoverflow.com/questions/8624146/does-c11-have-wrappers-for-dynamically-allocated-arrays-like-boosts-scoped-ar)。

template <class T>class SquareMatrixBase{protected:    SquareMatrixBase(size_t n_, T* p) :size(n_), pData(p){}//构造函数    void setDataPtr(T* p){ pData = p; }//set 函数    void invert()    {        cout << "invert" << " " << size << endl;        for (size_t i = 0; i < size*size; ++i)            cout << pData[i] << endl;    }private:    std::size_t size;    T* pData;};template<class T,std::size_t n>class SquareMatrix :private SquareMatrixBase < T >{public:    SquareMatrix() :SquareMatrixBase<T>(n, 0),        //pData(new T[n*n]{}, std::default_delete<T[]>())//shared_ptr需要deleter        pData(new T[n*n]{})    {        SquareMatrixBase<T>::setDataPtr(pData.get());//传给base    }    void invert()    {        SquareMatrixBase<T>::invert();    }    void SetData(T t[])    {        pData.reset(t);        SquareMatrixBase<T>::setDataPtr(pData.get());//传给base    }private:    //shared_ptr<T>pData;//shared_ptr这里除了需要指定deleter它不重载[]    unique_ptr<T[]>pData;};int main(){#if 1    SquareMatrix<int, 3>m1;    int *array1 = new int[3 * 3];    for (int i = 0; i < 9; ++i)    {        array1[i] = i + 1;    }    m1.SetData(array1);    m1.invert();// #endif    system("pause");    return 0;}

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

智能指针方便我们管理内存,原始指针方便做隐式转换,本条款通过实现智能指针的例子来讲述:

class A{};class B : public A{};template <class T>class SmartPtr{public:    explicit SmartPtr(T* t){}};int main(){    A *pa= new B;//ok    SmartPtr<A>pt1 = SmartPtr<B>(new B);//error    delete pa;    system("pause");    return 0;}

我们希望智能指针也能做到类之间的隐式转换,事实上,shared_ptr已经实现,不过我们就是要学习如何实现:

shared_ptr<A>pt1 = shared_ptr<B>(new B);//okpt1->func();//b::func

这里,我们需要为SmartPtr的构造函数写一个构造模板。即成员模板(member template)

template <class T>class SmartPtr{public:    template <class U>    SmartPtr(const SmartPtr<U>&other);};

以上代码的意思是,对任何类型T和任何类型U,这里可以根据SmartPtr生成SmartPtr只要U到T之间存在隐式转换关系。

1.成员函数模板表示泛化的模式,表示生成“可接受所有兼容类型”的函数
2.如果声明了泛化的拷贝构造函数和赋值操作符,那么也要声明正常的拷贝构造函数和赋值运算符

基于上述两点约束,根据书中的例子,实现一个简单的智能指针。

class A{public:    virtual void func(){ cout << "a::func" << endl; }    virtual  ~A(){}};class B : public A{public:    virtual void func(){ cout << "b::func" << endl; }};//////////////////////////////////////template <class T>class SmartPtr{public:    //返回use_count    int *use_count()const    {        return count;    }    //返回原始对象的指针    T*get()const    {        return heldPtr;    }    //析构函数    ~SmartPtr()    {        if (--(*count) == 0)        {            delete count;            delete heldPtr;            count = nullptr;            heldPtr = nullptr;        }    }//    //默认构造函数    SmartPtr() :count(nullptr), heldPtr(nullptr)    {    }    //使用内置指针初始化的构造函数    template <class U>    explicit SmartPtr(U * pu) : heldPtr(pu)    {        count = new int(1);    }    //泛化拷贝构造函数    template <class U>    SmartPtr(SmartPtr<U>&other) :count(other.use_count()),heldPtr(other.get())     {        ++(*count);     }    //拷贝构造函数    SmartPtr(SmartPtr&other) :count(other.use_count()), heldPtr(other.get())    {        ++(*count);    }    //泛化赋值符号重载    template<class U>    SmartPtr &operator=(const SmartPtr<U>&other)    {        ++(*other.use_count());        if ((count != nullptr) && (--(*count) == 0))        {            delete heldPtr;            delete count;        }        heldPtr = other.get();        count = other.use_count();        return *this;    }    //赋值符号重载    SmartPtr &operator=(const SmartPtr&other)     {        ++(*other.use_count());        if ((count!=nullptr)&&(--(*count) == 0))        {            delete heldPtr;            delete count;        }        heldPtr = other.get();        count = other.use_count();        return *this;    }    //->重载 作用类似于get()    T* operator->()const    {        return heldPtr;    }private:    int* count;    T* heldPtr;};int main(){    A *pa = new B;//ok    //B*pb = new B;    SmartPtr<A>global;    {        SmartPtr<B>pb(new B);        SmartPtr<A>pt1 = SmartPtr<B>(new B);//copy构造函数        global = pt1;//赋值函数        global = pb;        global->func();//        SmartPtr<A>pt2 = pt1;        pt1->func();    }    delete pa;    system("pause");    return 0;}
阅读全文
1 0