【effective c++读书笔记】【第7章】模板和泛型编程(2)

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

posted on 2015-08-16 13:34  ruan875417  阅读(192)  评论(0编辑  收藏  举报

导航