条款44:将与参数无关的代码抽离templates
template是节省时间和避免重复代码的一个奇妙方法。class template的成员函数只有在被使用时才被暗中具现化。但是如果不小心,使用templates可能导致代码膨胀(code bloat):其二进制代码带着重复(或几乎重复)的代码、数据、或两者。其结果可能源码看起来合身整齐,但目标码却不是那么回事。
假设你想为固定尺寸的正方矩阵编写一个template,并且该矩阵支持求逆矩阵计算:
#include<iostream> using namespace std; template<typename T, size_t n>//template支持n * n矩阵,元素类型是T class SquareMatrix{ //size_t我们称之为非类型参数 public: void invert(){ //求逆矩阵 cout << "求逆矩阵" << endl; } }; int main(){ SquareMatrix<double, 5> sm1; sm1.invert();//调用SquareMatrix<double, 5>::invert SquareMatrix<double, 10> sm2; sm2.invert();//调用SquareMatrix<double, 10>::invert system("pause"); return 0; }
上述例子具现化两份invert。其中一个操作5*5矩阵,另一个操作10*10矩阵,但除了常量5和10,invert函数的其他部分完全相同。这是template引出代码膨胀的一个典型例子。首先修改如下:把矩阵的大小变成invert函数的参数。
#include<iostream> using namespace std; template<typename T>//于尺寸无关的base class,用于正方矩阵 class SquareMatrixBase{ protected: void invert(size_t matrixSize){ cout << "求逆矩阵" << endl; } }; template<typename T, size_t n> class SquareMatrix : private SquareMatrixBase<T>{ private: using SquareMatrixBase<T>::invert;//避免遮掩base版的invert public: void invert(){ this->invert(n);//inline调用base版的invert } }; int main(){ SquareMatrix<double, 5> sm1; sm1.invert(); SquareMatrix<double, 10> sm2; sm2.invert(); system("pause"); return 0; }
上述例子中,带参数的invert在base class SquareMatrixBase中,SquareMatrixBase只对矩阵对象的类型参数化,所以对于给定元素对象类型,所有矩阵共享同一个也是唯一一个SquareMatrixBase class,它们也将共享SquareMatrixBaseclass内的invert。
这个例子要注意两点:
a、使用using声明和this->指针
b、这里的base class只是为了帮助derived class实现,并不是为了表现SquareMatrix和SquareMatrixBase之间的is-a关系,所以用private继承
上述例子还有问题,SquareMatrixBase::invert如何知道该操作什么数据呢?数据放在哪里?令SquareMatrixBase贮存一个指针,指向矩阵数值所在的内存:
#include<iostream> using namespace std; template<typename T> class SquareMatrixBase{ public: SquareMatrixBase(size_t n, T* pMem):size(n), pData(pMem){} void setDataPtr(T* ptr){ pData = ptr; } void invert(){ //运用size和pData求逆矩阵 cout << "求逆矩阵" << endl; } private: size_t size;//矩阵的大小 T* pData;//指向矩阵内容 }; template<typename T, size_t n> class SquareMatrix : private SquareMatrixBase<T>{ public: SquareMatrix():SquareMatrixBase<T>(n, data){} void invert(){ //inline调用base版的invert SquareMatrixBase<T>::invert(); } private: T data[n*n]; }; int main(){ SquareMatrix<double, 5> sm1; sm1.invert(); SquareMatrix<double, 10> sm2; sm2.invert(); system("pause"); return 0; }
上述做法对象不需要动态分配内存,但对象自身可能非常大。可以把每一个矩阵的数据放进heap:
#include<boostsmart_ptr.hpp> #include<iostream> using namespace std; using namespace boost; template<typename T> class SquareMatrixBase{ public: SquareMatrixBase(size_t n, T* pMem):size(n), pData(pMem){} void setDataPtr(T* ptr){ pData = ptr; } void invert(){ //运用size和pData求逆矩阵 cout << "求逆矩阵" << endl; } private: size_t size;//矩阵的大小 T* pData;//指向矩阵内容 }; template<typename T, size_t n> class SquareMatrix : private SquareMatrixBase<T>{ public: SquareMatrix():SquareMatrixBase<T>(n, 0),pData(new T[n*n]){ this->setDataPtr(pData.get()); } void invert(){ //inline调用base版的invert SquareMatrixBase<T>::invert(); } private: scoped_array<T> pData; }; int main(){ SquareMatrix<double, 5> sm1; sm1.invert(); SquareMatrix<double, 10> sm2; sm2.invert(); system("pause"); return 0; }
上面只讨论了非类型模板参数带来的膨胀,类型参数也会导致膨胀。如许多平台上的int和long有相同的二进制表示,所以像vector<int>和vector<long>的成员函数有可能完全相同,这正是膨胀的最佳定义。在大多数平台上,所有的指针类型都有相同的二进制表述,因此凡templates持有指针者往往应该对每一个成员函数使用唯一一份底层实现。
请记住:
- Template生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
- 因非类型模版参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。
- 因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码。
条款45:运用成员函数模板接受所有兼容类型
1、所谓智能指针是“行为像指针”的对象,并提供指针没有的机能。
真实指针做的很好的一件事是支持隐式转换。Derivedclass指针可以隐式转换为base class指针。指向non-const的对象的指针可以转换为指向const对象。
但是同一个template的不同具现体之间并不存在什么与生俱来的固有关系(如果带有base-derived关系的B,D两类型分别具现化某个template,产生出的两个具现体并不带有base-derived关系),所以编译器视SmartPtr<Middle>和SmartPtr<Top>为完全不同的classes。为了获得我们希望的SmartPtr class之间的转换能力,我们必须将它们明确地编写出来。
例子:
//Smart.h #ifndef SMART_H #define SMART_H template<typename T> class SmartPtr{ public: SmartPtr(T* realPtr = 0); //构造函数 ~SmartPtr();//析构函数 SmartPtr(const SmartPtr& rhs); //拷贝构造函数 template<typename U> SmartPtr(const SmartPtr<U>& rhs);//泛化拷贝构造函数 SmartPtr& operator=(const SmartPtr& rhs);//拷贝赋值运算符 template<typename U> SmartPtr& operator=(const SmartPtr<U>& rhs);//泛化拷贝赋值运算符 T* get() const; //获取原始指针 T* operator->() const;//重载->运算符 T& operator*() const;//重载*运算符 bool operator!() const;//重载!运算符 private: T* pointer; }; //构造函数 template<typename T> SmartPtr<T>::SmartPtr(T* realPtr = 0) :pointer(realPtr){} //析构函数 template<typename T> SmartPtr<T>::~SmartPtr(){ delete pointer; } //拷贝构造函数 template<typename T> SmartPtr<T>::SmartPtr(const SmartPtr<T>& rhs){ pointer = rhs.pointer; rhs.pointer = 0; } //泛化拷贝构造函数 template<typename T> template<typename U> SmartPtr<T>::SmartPtr(const SmartPtr<U>& rhs) :pointer(rhs.get()){ } //拷贝赋值运算符 template<typename T> SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<T>& rhs){ if (this == &rhs) return *this; delete pointer; pointer = rhs.pointer; rhs.pointer = 0; return *this; } //泛化拷贝赋值运算符 template<typename T> template<typename U> SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<U>& rhs){ if (this == &rhs) return *this; delete pointer; pointer = rhs.get(); return *this; } //获取原始指针 template<typename T> T* SmartPtr<T>::get() const{ return pointer; } //重载->运算符 template<typename T> T* SmartPtr<T>::operator->() const{ return pointer; } //重载*运算符 template<typename T> T& SmartPtr<T>::operator*() const{ return *pointer; } //重载!运算符 template<typename T> bool SmartPtr<T>::operator!() const{ if (pointer == nullptr) return true; return false; } #endif //Tmb.h #ifndef TMB_H #define TMB_H #include<iostream> class Top{ public: Top(int i = 0){ iTop = i; } void printT(){ std::cout << iTop << std::endl; } private: int iTop; }; class Middle:public Top{ public: Middle(int i = 0):Top(i){} void printM(){ printT(); } }; class Bottom:public Middle{ public: Bottom(int i = 0):Middle(i){} void printB(){ printM(); } private: int iTop; }; #endif #include"Smart.h" #include"Tmb.h" using namespace std; //main.cpp int main(){ Top* pt1 = new Middle; Top* pt2 = new Bottom; const Top* pct = pt1; SmartPtr<Top> spt1 = SmartPtr<Middle>(new Middle); SmartPtr<Top> spt2 = SmartPtr<Bottom>(new Bottom); SmartPtr<const Top> spct = spt1; //SmartPtr<Middle> spt3 = SmartPtr<Top>(new Top);//编译出错,无法从“Top *”转换为“Middle *” system("pause"); return 0; }
上述例子中,我们为SmartPtr写了泛化拷贝构造函数和泛化拷贝赋值运算符,为了SmartPtrclass之间能互相转换。
上述例子要注意以下几点:
a、我们希望根据SmartPtr<Middle>创建一个SmartPtr<Top>,但不希望一个SmartPtr<Top>创建一个SmartPtr<Middle>。上述泛化拷贝函数中用成员初始列来初始化SmartPtr<T>内类型为T*的成员变量,并以U*作为初值。这个行为只有当“存在某个隐式转化可将一个U*指针转换为一个T*指针”时才能通过编译,那正是我们想要的。
b、在类内声明一个泛化拷贝构造函数(是个member template)并不阻止编译器生成它们自己的拷贝构造函数(一个non-template),所以如果你想要控制拷贝构造函数的方方面面,你必须同时声明泛化拷贝构造函数和“正常的”拷贝构造函数。相同规则也适用于赋值操作符。
请记住:
- 请使用member function templates(成员函数模版)生成“可接受所有兼容类型”的函数。
- 如果你声明member templates用于“泛化copy构造”或“泛化赋值操作符”,你还是需要声明正常的copy构造函数和copy assignment操作符。
版权声明:本文为博主原创文章,未经博主允许不得转载。