欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Effective C++ 条款24、25

程序员文章站 2022-04-01 20:40:48
...

条款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,如有错误地方,欢迎指正!相互学习,促进!!