Effective C++笔记—模板与泛型编程

Posted NearXDU

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Effective C++笔记—模板与泛型编程相关的知识,希望对你有一定的参考价值。

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


条款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 List
publicexplicit 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×5 10×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);//ok
pt1->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;

以上是关于Effective C++笔记—模板与泛型编程的主要内容,如果未能解决你的问题,请参考以下文章

《Effective C++》读书笔记汇总

《Effective C++》阅读笔记 条款01:视C++为一个语言联邦

c++模板与泛型编程

Effective C++读书笔记

Boolan STL与泛型编程第一周笔记

c++标准容器库与泛型编程