chapter 4 设计与声明
item24:当所有参数都需要隐式类型转换的时候,使用non-member函数
1)如果一个函数的每一个参数都需要一个类型转换(包括可能的this指针),那么这个函数一定是一个non-member函数。下面一步步来分析,假设有一个实数类,需要进行乘法运算:
//实数类的声明
class Rational {
public:
// ctor is deliberately not explicit;allows implicit int-to-Rational conversions
Rational(int numerator = 0,int denominator = 1);
int numerator() const; // accessors for numerator and
int denominator() const; // denominator — see Item 22
private:
...
public:
//假设实数类里面有一个operator*的函数,先不考虑上一个item讲得关于封装性的考虑
const Rational operator*(const Rational& rhs) const;
};
//如果这样调用,OK
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth; // fine
result = result * oneEighth; // fine
//但是,如果需要进行混合运算
result = oneHalf * 2; // fine 代码1
result = 2 * oneHalf; // error! 代码2
//上述代码的等价形式
result = oneHalf.operator*(2); // fine
result = 2.operator*(oneHalf); // error!
//代码1的进一步的等价形式
const Rational temp(2); // create a temporary Rational object from 2
result = oneHalf * temp; // same as oneHalf.operator*(temp);
按理来说,乘法运算是可以交换的,没理由说支持某一种顺序,而另一种顺序就是错误的。当然如果Rational的构造函数是explicit的,那么代码1也是错误的,达到一致性了,呵呵。现在分析代码2,由于2是一个整型,所以没有对应的operator*函数,所以编译器会在non-member函数里面去寻找类似于result = operator*(2, oneHalf);但是这个函数不存在,所以最后会出现编译错误。为什么不能上面两个代码,一个可以,而另一个不可以呢。先说代码1,2作为operator*的参数,传进去的时候进行了一个隐式转换,而第二种方法,2是this指针所指的,不是通过参数传的,所以没办法转化。所以这里需要一个non-member函数。
const Rational operator*(const Rational& lhs,const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
}
Rational oneFourth(1, 4);
Rational result;
result = oneFourth * 2; // fine
result = 2 * oneFourth; // hooray, it works!
2)那这里需不需要把operator*作为friend函数呢?答案是no,因为operator*完全可以由Rational的公共接口实现。当然这也揭示了一个道理:与member function对应的是non-member function而不是friend function。不是说一个函数不是member function就一定是friend function。现实生活中,可能朋友也会带来麻烦呢,是吧~
3)当涉及到template C++而不是面向对象C++时,可能会有新的情况需要考虑(见item 46)。so,keep moving.
item25:考虑写出一个不抛出异常的swap函数
1)如果swap的缺省实现码对你的class或者class template提供可接受的效率,那你不需要额外任何事。
2)如果缺省的swap实现版的效率不足(比如你的class或者template中包含了指向实现的指针(pimpl idiom),那么就可以尝试着做一下几件事来提高swap效率。
3)提供一个public swap成员函数,高效的置换类型的两个对象值,比如说涉及到指针的时候,只置换指针的地址即可。这个函数绝不该抛出异常。因为swap缺省版本是以copy构造函数和copy赋值操作符为基础的,而一般情况下两者都允许抛出异常。而你特化的swap一般来说跟默认的swap具有相同的特性。因为你写的swap基本上都是对内置类型进行操作,而内置类型上的操作绝不会抛出异常。
4)在你的class或者template所在的命名空间提供一个non-member的swap,如同item24里面所讲的一样,然后用它来调用上述的swap成员函数。当然如果你的class的定义和这个non-member的swap如果不是另外放在一个命名空间中(也就是和别的class或者typedef什么的混在一起),其功能也可以实现,但是这样一来,不觉得代码很乱?如果是class(非template),同时为class特化std::swap。(PS:说实话,感觉这句话我没理解,可能是因为我对C++的泛型编程不了解,当然希望有人路过的时候留言讨论,如果有新的感悟,我再加上来)
//4)的代码说明
namespace WidgetStuff {
... // templatized WidgetImpl, etc.
template<typename T> // as before, including the swap
class Widget { ... }; // member function
...
template<typename T> // non-member swap function;
void swap(Widget<T>& a,Widget<T>& b) // not part of the std namespace
{
a.swap(b);
}
}
//5)的代码说明
template<typename T>
void doSomething(T& obj1, T& obj2)
{
using std::swap; // make std::swap available in this function
...
//正确的调用方式,call the best swap for objects of type T
swap(obj1, obj2);
//错误的调用方式,这样编译器就会默认的只在std命名空间去查找合适的swap函数
std::swap(obj1, obj2);
...
}
5)如果调用swap,记得包含一个using声明,使得std::swap可用,并且不要加任何修饰符,直接调用swap。
6)为"用户定义类型"进行std template全特化是好的,但是不要尝试在std内加入某些对std而言全新的东西。