Effective C++ 条款24、25

条款24 若所有参数皆需类型转换,请为此采用non-member函数

如果需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必有是个non-member.

如下代码举例:

class Rational {
public:
	Rational(int numeator = 0, int denominator = 1);//构造函数刻章不为explicit;
                                                     //允许int-to-Rational隐式转换
	const Rational operatior*(const Rational& rhs)const;
	int numeator()const;
	int denominator()const;
private:
...
};

Rational oneEigth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneEigth*oneHalf;
result = resutl*oneEigth;

result = oneHalf * 2;//good! 此语句等效于result=oneHalf.operator*(2);   此处还能成功的原因是因为Rational构造函数允许进行隐式类型转换
result = 2 * oneHalf;//error!error!error!error! 此语句等效于result=2.operator*(oneHalf);

上述result=2*oneHalf;语句写成等效就知道其错误。另外编译器也会寻找像result=operator*(2,oneHalf);语句。但Rational类中不存在这样一个接受int和Rational作为参数的non-member operator*。因此查找失败。

很显然上述的乘法不满足交换律。

如果*号两边的对象都需要类型转换,此时将operator*()写成non-member函数,可以完美解决问题。

const Rational operator*(const Rational& lhs, const Rational& rhs) {
	return Rational(lhs.numeator()*rhs.numeator(), lhs.denominator()*rhs.denominator());
}
Rational oneFourth(1, 4);
Rational result;
result = oneFourth * 2;//perfect!
result = 2 * oneFourth;//perfect!

让operator*成为一个non-member函数,允许编译器在每一个实参身上执行隐式类型转换。(隐约记得,在c++ primer上建议双目运算符写成非成员函数哈,这是有道理滴!)

条款25 考虑写一个不抛异常的swap函数

i、当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。

ii、如果提供一个member swap,也该提供一个non-member swap用来调用前者,对于classes(而非templates),也请特化std::swap。

iii、调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”。

iv、为“用户定义类型”进行std templates全特化是好的,但行万不要尝试在std内加入某些对std而言全新的东西。

//以下是缺省情况下swap动作且由标准程序库提供的swap算法完成
namespace std {
	template<typename T>
	void swap(T& a, T& b) {
		T temp(a);
		a = b;
		b = temp;
	}
}
上述缺省swap函数对pimpl(pimpl是“pointer to implementation”)手法(以指针指向一个对象,内含真正数据)的类型,表现的非常低效(因为此时只需要交换两者的指针,而无需复制底层的数据,缺省swap函数却不是这样做的)。给出的建议如ii。具体实现如下代码:
class WidgetImpl {//针对Widget数据而设计的class
public:
	...
private:
	int a, b, c;
	vector<double> v;//由于有许多数据,意味复制时间很长
};
class Widget {
public:
	Widget(const Widget& rhs);
	Widget& operator*(const Widget& rhs) {
		*pImpl = *(rhs.pImpl);
	}
	//在类内提供一个member swap,也该提供一个non-member swap用来调用前者
	void swap(Widget& other) {
		using std::swap;//此声明,C++编译器将优先使用专属的swap,如果没有再使用std空间内的swap。但编译器更喜欢专属特版化的swap,用法见iii
		swap(pImpl, other.pImpl);
	}
private:
	WidgetImpl* pImpl;//指针,所指对象内含Widget数据
};

namespace std {//在std空间中特化了的swap
	template<>
	void swap<Widget>(Widget& a, Widget& b) {
		a.swap(b);
	}
}

上述做法与STL容器有一致性,very good!

但设想一下,如果Widget和WidgetImpl都是class templates而非classes。如下所示,

template<typename T>
class WidgetImpl{...};
template<typename T>
class Widget{...};

//以下偏特化过程不合法
namespace std {
	template<typename T>
	void swap<Widget<T>>(Widget<T>& a, Widget<T>& b) {
		a.swap(b);
	}
}

上述主要错误原因:企图偏特化一个function template,但C++只允许对class template偏特化,在function templates身上偏特化是行不通的。于是,通过重载swap函数达到上述目的(又不太好,因为C++允许客户全特化std内的template,但不可以添加新的templates或class或functions或其他任何东本到std里头)。

//不太好,因为C++允许客户全特化std内的template,
//但不可以添加新的templates或class或functions或其他任何东本到std里头
namespace std {
	template<typename T>
	void swap(Widget<T>& a, Widget<T>& b) {
		a.swap(b);
	}
}
正确解决上问题的方法, 另起一个命名空间,在其中声明定义类,重载swap函数。
namespace WidgetStuff {
	template<typename T>
	class Widget{...};//定义同上

	template<typename T>
	void swap(Widget<T>& a, Widget<T>& b) {//此处不属于std空间,这样做是完全允许的
		a.swap(b);
	}
}

以上内容均来自Scott Meyers大师所著Effective C++ version3,如有错误地方,欢迎指正!相互学习,促进!!

猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80562461