条款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<boost\smart_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操作符。