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,如有错误地方,欢迎指正!相互学习,促进!!
推荐阅读
-
Effective C++ 笔记:条款 31 将编译关系降至最低
-
Effective Modern C++ 条款32 对于lambda,使用初始化捕获来把对象移动到闭包
-
Effective Modern C++ 条款23 理解std::move和std::forward
-
Effective Modern C++ 条款37 在所有路径上,让std::thread对象变得不可连接(unjoinable)
-
Effective Modern C++ 条款38 意识到线程句柄的析构函数的不同行为
-
Effective Modern C++ 条款22 当使用Pimpl Idiom时,在实现文件中定义特殊成员函数
-
Effective C++ 笔记:条款 33 避免继承导致的名称遮掩
-
Effective C++:条款26:尽可能延后变量定义式的出现时间
-
Effective C++ 条款26 尽可能延后变量定义式的出现时间
-
Effective C++条款50:定制new和delete之(了解new和delete的合理替换时机)