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