Effective C++学习笔记之第四章(5)

chapter 4 设计与声明

item24:当所有参数都需要隐式类型转换的时候,使用non-member函数
1)如果一个函数的每一个参数都需要一个类型转换(包括可能的this指针),那么这个函数一定是一个non-member函数。下面一步步来分析,假设有一个实数类,需要进行乘法运算:

  1. //实数类的声明  
  2. class Rational { 
  3. public
  4.   // ctor is deliberately not explicit;allows implicit int-to-Rational conversions  
  5.   Rational(int numerator = 0,int denominator = 1);   
  6.   int numerator() const;             // accessors for numerator and  
  7.   int denominator() const;           // denominator — see Item 22  
  8. private
  9.   ... 
  10. public
  11.   //假设实数类里面有一个operator*的函数,先不考虑上一个item讲得关于封装性的考虑  
  12.   const Rational operator*(const Rational& rhs) const
  13. }; 
  14. //如果这样调用,OK  
  15. Rational oneEighth(1, 8); 
  16. Rational oneHalf(1, 2); 
  17. Rational result = oneHalf * oneEighth; // fine  
  18. result = result * oneEighth;           // fine  
  19. //但是,如果需要进行混合运算  
  20. result = oneHalf * 2;         // fine    代码1  
  21. result = 2 * oneHalf;        // error!  代码2  
  22. //上述代码的等价形式  
  23. result = oneHalf.operator*(2);   // fine   
  24. result = 2.operator*(oneHalf);  // error!  
  25. //代码1的进一步的等价形式  
  26. const Rational temp(2);   // create a temporary Rational object from 2  
  27. result = oneHalf * temp;  // same as oneHalf.operator*(temp); 
//实数类的声明
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函数。

  1. const Rational operator*(const Rational& lhs,const Rational& rhs) 
  2.   return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator()); 
  3. Rational oneFourth(1, 4); 
  4. Rational result; 
  5. result = oneFourth * 2;  // fine  
  6. result = 2 * oneFourth;  // hooray, it works! 
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++的泛型编程不了解,当然希望有人路过的时候留言讨论,如果有新的感悟,我再加上来)

  1. //4)的代码说明  
  2. namespace WidgetStuff { 
  3.   ...                                     // templatized WidgetImpl, etc.  
  4.   template<typename T>                    // as before, including the swap  
  5.   class Widget { ... };                   // member function  
  6.   ... 
  7.   template<typename T>                    // non-member swap function;  
  8.   void swap(Widget<T>& a,Widget<T>& b)   // not part of the std namespace                                                      
  9.   { 
  10.     a.swap(b); 
  11.   } 
  12. //5)的代码说明  
  13. template<typename T> 
  14. void doSomething(T& obj1, T& obj2) 
  15.   using std::swap;           // make std::swap available in this function  
  16.   ... 
  17.   //正确的调用方式,call the best swap for objects of type T  
  18.   swap(obj1, obj2);          
  19.   //错误的调用方式,这样编译器就会默认的只在std命名空间去查找合适的swap函数  
  20.   std::swap(obj1, obj2); 
  21.   ... 
//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而言全新的东西。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值