effective C++笔记--模板与泛型编程(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rest_in_peace/article/details/84562984

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

. 真实指针做的很好的一件事是支持隐式转换,派生类的指针可以指向基类的指针,指向非常量对象的指针可以指向转换成常量对象的指针等。但是如果想在用户自定义的智能指针中模拟上述转换,稍稍会有点麻烦。例如:

class Top{...};
class Middle:public Top{...};
class Bottom:public Middle{...};

template <typename T>
class SmartPtr{
public:
	explicit SmartPtr(T* realPtr);		//智能指针通常以内置指针完成初始化
};
SmartPtr<Top> pt1 = SmartPtr<Middle>(new  Middle);
SmartPtr<Top> pt2 = SmartPtr<Bottom>(new  Middle);
SmartPtr<const Top> pct1 = pt1;

. 可惜上述代码不能通过编译,因为同一个template的不同具现体之间并不存在什么与生俱来的固有关系,所以编译器将SmartPtr<Middle>和SmartPtr<Top>视为完全不同的class。因此为了获得转换能力,必须将它们明确的编写出来。
  在上述的智能指针实例中,每一条语句创造了一个新的智能指针对象,所以应该关注的是如何让编写智能指针的构造函数,使其行为能满足转型需要。但是一个很关键的观察结果是:无法写出所有需要的构造函数,即使根据现有的SmartPtr<Middle>和SmartPtr<Bottom>能构造SmartPtr<Top>,但是在以后新的类假如这个继承体系的时候,还需要继续添加,就原理而言,这样需要的构造函数将没有止境。因此,需要的不是给SmartPtr写一个构造函数,而是为他写一个构造模板。 这样的模板就是所谓的成员函数模板,其作用是为class生成函数:

template <typename T>
class SmartPtr{
public:
	template <typename U>
	SmartPtr(const SmartPtr<U>& other);		//为了生成拷贝构造函数
};

. 以上代码的意思是,对任何类型T和任何类型U,可以根据SmartPtr<U>生成一个SmartPtr<T>。上述泛化的拷贝构造函数并没有被声明为explicit,因为原始指针之间的转换是隐式转换的,无需明确写出转型动作。
  完成声明后,这个泛化构造函数提供的东西比需要的更多。我们的目的应该只是希望根据SmartPtr<Bottom>创建SmartPtr<Top>,但是不希望SmartPtr<Top>创建SmartPtr<Bottom>,这对public继承而言是矛盾的。同时也不希望根据一个SmartPtr<double>创建SmartPtr<int>,因为现实中没有将int转换成double的隐式转换,所以,必须从某方面对这些构造函数进行筛选。
  假设SmartPtr遵循auto_ptr和tr1::shared_ptr所提供的榜样,也提供一个get成员函数,返回智能指针对象所持有的那个原始指针的副本,那么可以在构造模板实现代码中进行约束,使它更合理:

template <typename T>
class SmartPtr{
public:
	template <typename U>
	SmartPtr(const SmartPtr<U>& other)		//以other的heldPtr来
		:heldPtr(other.get()){...}			//初始化this的heldPtr
	T* get() const {return heldPtr;}
private:
	T* heldPtr;						//所持有的内置(原始)指针
};

. 使用initialzation list来初始化SmartPtr<T>内的类型为T的成员变量,并以类型为U的指针作为初值,这个行为只有在“存在某个隐式转换可将一个U指针转为一个T指针”时才能通过编译,这正是我们想要的。
  成员函数模板的效用不限于构造函数,另一个常用的效用是支持赋值操作,例如shared_ptr支持所有“来自兼容之内置指针、shared_ptrs、auto_ptrs和
weak_ptrs“的构造行为,以及所有来自上述物的赋值操作,比如shared_ptr的一部分定义:

template<class T>
class shared_ptr{
public:
	template<class Y>
	explicit shared_ptr(Y* p);				//构造,来自任何兼容的内置指针
	template<class Y>
	shared_ptr(shared_ptr<Y> const& r);		//或shared_ptr
	template<class Y>
	explicit shared_ptr(weak_ptr<Y> const& r);	//或weak_ptr
	template<class Y>
	explicit shared_ptr(auto_ptr<Y> const& r);		//或auto_ptr
	template<class Y>
	shared_ptr& operator=(shared_ptr<Y> const& r);	//赋值,来自
													//任何兼容的shared_ptr
	template<class Y>								
	shared_ptr& operator=(auto_ptr<Y>& r);			//或auto_ptr
	
};

. 上述代码表示从某个shared_ptr隐式转换至另一个shared_ptr是允许的但从某个内置指针或是其他智能指针进行隐式转换则不被认可。另外关于auto_ptr的复制构造函数和赋值操作符都未被声明为const的,这是因为当你复制一个auto_ptr,其实它已经被改动了。
  成员函数模板并不改变语言基本规则,在class内声明一个泛化的拷贝构造函数并不会阻止编译器声生成自己的拷贝构造函数(如果你没有自己声明的话),相同的规则也适用于赋值操作。

需要类型转换时请为模板定义非成员函数

. 如之前的条款”若所有的参数皆需要类型转换,请为此采用non-member函数“一样,可能在模板编程的时候也能支持类似的复数之类的混合运算。所以可能写出以下代码:

template<typename T>
class Rational{
public:
	Rational(const T& numerator = 0,const T& denominator = 1);
	const T numerator() const;
	const T denominator() const;
};

template<typename T>
const Rational<T> operator* (const Rational<T>& lhs,const Rational<T>& rhs){
	...
}

//调用
Rational<int> oneHalf(1,2);				
Rational<int> result = oneHalf * 2;			

. 遗憾的是,两个调用都会报错,看来模板化的Rational内的某些东西似乎和非模板的版本有所不同。事实也确实如此,在非模板的版本中,编译器知道我们尝试调用什么函数,但在这里,编译器不知道想要调用什么函数,取而代之的是,它试图想出什么函数被名为operator的template具现化出来。它们知道应该可以具现化出某个”名为operator并接受两个参数“的函数,但为了完成这个操作,必须要知道T是啥,可惜它没这个能耐。
  以上述代码来分析:传给operator函数的第一个参数被声明为Rational<T>,传递给它的第一个实参是Rational<int>,所以T是int,但是第二个参数被声明为Rational<T>,传递给它的第二个实参是int,编译器没办法根据这个推断出T,或许你希望编译器能自己做隐式转换,但是在template的实参推导过程中从不将隐式类型转换函数纳入考虑
  只要利用一个事实,就可以缓和template实参推导方面遇到的挑战:template class内的friend声明式可以指涉某个特定函数。那意味着class Rational<T>可以声明operator
是它的一个friend函数。class template并不依赖template实参推导,所以编译器总是能够在class template<int>具现化的时候得知T:

template<typename T>
class Rational{
public:
	...
	//不声明为Rational<T>只是为了看起来简洁点
	friend const Rational operator*(const Rational& lhs,const Rational& rhs);
};

template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs){
	...
}

. 这样编写后,能通过编译,但是不能通过链接,这是因为这个函数只是被声明与Rational内,并没有被定义出来,虽然意图在外部的operator* template提供定义式,但是行不通——如果我们自己声明了一个函数,就有责任定义它。既然没有提供定义式,自然链接出错。
  最简单的方式是将它的函数本体合并到声明式内:

template<typename T>
class Rational{
public:
	...
	friend const Rational operator*(const Rational& lhs,const Rational& rhs){
		return Rational(lhs.numerator() * rhs.numerator(),
						lhs.denominator() * rhs.denominator());
	}
};

. 万幸,对operator的调用现在可编译连接并执行了。
  定义于class内部的函数都暗自成为inline函数,包括像operator
这样的friend函数,可以将inline声明带来的冲击最小化,做法是令operator不做任何事,只调用一个定义于class外部的辅助函数,但在本例中没有意义,因为operator已经是一个单行函数了,但对更复杂的函数是可以试试的。这样的辅助函数通常也是一个template,会声明在头文件中,并且许多编译器实质上会强迫你把所有的template定义式都放入头文件内。

猜你喜欢

转载自blog.csdn.net/rest_in_peace/article/details/84562984