Effective C++读书笔记(2)

条款03:尽可能使用const

Use const whenever possible.

         const允许你告诉编译器和其他程序员某值应该保持不变。

         如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。

1. char greeting[] = "Hello"; 

2. char* p = greeting;         //non-const pointer, non-const data 

3. const char* p = greeting;       //non-const pointer, const data 

4. char* const p = greeting;       //const pointer, non-const data 

5. const char* const p = greeting; //const pointer, const data 

         如果被指物是常量,既可以关键字const写在类型之前,又可以把它写在类型之后、星号之前。两种写法的意义等价:

1.  void f1(const Widget* pw);    //f1获得一个指针,指向一个常量Widget对象..  

2.  void f2(Widget const * pw);   //f2也是 

         STL迭代器系以指针为底层塑模出来,所以迭代器的作用就像个T*指针。声明迭代器为const就像声明指针为const一样(即声明一个T* const 指针),表示这个迭代器不得指向不同的东西,但它所指的东西的值是可以改动的。如果希望迭代器所指的东西(数据)不可被改动(即希望STL模拟一个const T* 指针),则用const_iterator:

1.  std::vector<int> vec;  
2.  ...  
3.  const std::vector<int>::iterator iter = vec.begin( ); //T* const
4.  *iter = 10;                     //没问题,改变iter所指物  
5.  ++iter;                         //错误!iter是const  
6.  std::vector<int>::const_iterator cIter = vec.begin( );// const T*  
7.  *cIter = 10;                        //错误! *cIter是const  
8.  ++cIter;                        //没问题,改变cIter。 

         令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。

1.  class Rational { ... };  
2.  const Rational operator* (const Rational& lhs,const Rational& rhs); 

    为什么返回一个const对象?原因是如果不这样客户就能实现这样的行为:

1.  Rational a, b, c;  
2.  ...  
3.  (a * b) = c;        //在a * b的成果上调用operator= 

         如果a和b都是内置类型,这样的代码直截了当就是不合法。而一个"良好的用户自定义类型"的特征是它们避免无端地与内置类型不兼容。将operator* 的回传值声明为const可以预防那个荒唐的赋值动作。

 

const成员函数

         将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。这类成员函数可以得知哪个函数可以改动对象内容而哪个函数不行,很是重要。

         两个成员函数如果只是常量性不同,可以被重载。这实在是一个重要的C++特性(前几天的面试刚碰到过):

1.  class TextBlock {  
2.  public:  
3.    ...  
4.    const char& operator[](std::size_t position) const 
5.    { return text[position]; }        // operator[] for const对象.  
6.    char& operator[](std::size_t position)       
7.    { return text[position]; }        // operator[] for non-const对象.  
8.  private:  
9.    std::string text;  
10. };  
11.  
12. TextBlock tb("Hello");  
13. std::cout << tb[0];     //调用non-const TextBlock::operator[]  
14. const TextBlock ctb("World");  
15. std::cout << ctb[0];        //调用const TextBlock::operator[] 

         只要重载operator[]并对不同的版本给予不同的返回类型,就可以令const和non-const TextBlocks获得不同的处理:

1.  std::cout << tb[0];//没问题 - 读一个non-const TextBlock  
2.  tb[0] = 'x';        //没问题 - 写一个non-const TextBlock  
3.  std::cout << ctb[0];//没问题 - 读一个const TextBlock  
4.  ctb[0] = 'x';   //错误! - 写一个const TextBlock 

         上述错误只因operator[] 的返回类型以致,至于operator[] 调用动作自身没问题。

         请注意,non-const operator[] 的返回类型是个reference tochar,不是char。如果operator[]只是返回一个char,下面这样的句子就无法通过编译:

1.  tb[0] = 'x'; 

         返回类型是内置类型的函数,改动函数返回值不合法。纵使合法,C++以值传递意味被改动的其实是tb.text[0]的一个副本,不是tb.text[0]自身。

将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。

 

         成员函数如果是const意味什么?

         bitwise const:成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是const,也就是说它不更改对象内的任何一个bit。bitwise constness正是C++ 对常量性的定义,因此const成员函数不可以更改对象内任何non-static成员变量。

         不幸的是许多成员函数虽然不十足具备const性质却能通过bitwise测试:

1. class CTextBlock {  

2. public:  

3.    ... 

4.    char& operator[](std::size_t position) const // bitwise const声明,  

5.    { return pText[position]; }      // 但其实不适当.跟之前相比少了一个const!

6. private:  

7.    char* pText; 

8. };

         operator[]实现代码并不更改私有变量pText,于是编译器为operator[]产出目标码,并认定它是bitwiseconst。

1.  const CTextBlock cctb("Hello");//声明一个常量对象。  
2.  char* pc = &cctb[0];//调用const operator[]取得一个指针, 指向cctb的数据。  
3.  *pc = 'J';              //cctb现在有了 "Jello" 这样的内容。 

         以上代码没有任何错误:创建一个常量对象并设以某值,而且只对它调用const成员函数。但终究还是改变了它的值。

         这种情况导出所谓的logical constness:一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此,也就是自己可以改用户不能改。

1.  class CTextBlock {  
2.  public:  
3.     ...  
4.     std::size_t length() const;  
5.  private:  
6.     char* pText;  
7.     std::size_t textLength; //最近一次计算的文本区块长度。  
8.     bool lengthIsValid; //目前的长度是否有效。  
9.  };  
10. std::size_t CTextBlock::length() const  
11. {  
12.    if (!lengthIsValid) {  
13.       textLength = std::strlen(pText);
14.       //错误!在const成员函数内不能对私有变量textLength进行修改  
15.       lengthIsValid = true;       
16.       //错误!在const成员函数内不能对私有变量lengthIsValid进行修改 
17.    }                           
18.    return textLength;  }

         解决办法很简单:利用C++ 中的mutable(可变的)修饰符,mutable释放掉non-static成员变量的bitwiseconstness约束:

1.  class CTextBlock {  
2.  public:  
3.     ...  
4.     std::size_t length() const;  
5.  private:  
6.     char* pText;  
7.     mutable std::size_t textLength; //这些成员变量可能总是会被更改,                  
8.     mutable bool lengthIsValid;     //即使在const成员函数内。 
9.  };                              //现在,刚才的length函数就可以了~ 

编译器强制实施bitwise constness,但你编写程序时应该使用"概念上的常量性"(conceptual constness)。

 

在const和non-const成员函数中避免重复

         对于"bitwise-constness非我所欲"的问题,mutable是个解决办法,但它不能解决所有的const相关难题。举个例子,假设TextBlock(和 CTextBlock)内的operator[] 不单只是返回一个reference指向某字符,也执行边界检验(boundschecking)、志记访问信息(loggedaccess info.)、甚至可能进行数据完善性检验。把所有这些同时放进const和non-const operator[] 中,导致两个版本的operator[]及大量的代码重复。

         真正该做的是实现operator[]的机能一次并使用它两次,也就是说,令其中一个调用另一个。本例中constoperator[]完全做掉了non-const版本该做的一切,唯一的不同是其返回类型多了一个const资格修饰。

1.  class TextBlock {  
2.  public:  
3.    ...  
4.    const char& operator[](std::size_t position) const //一如既往  
5.    {  
6.      ...  
7.      return text[position];  
8.    }  
9.    char& operator[](std::size_t position)    //现在只调用const op[]  
10.   {  
11.      return  
12.         const_cast<char&>(          //将op[]返回值的const转除  
13.           static_cast<const TextBlock&>(*this)//为*this加上const  
14.               [position]                    //调用const op[]  
15.         );  
16.   }  
17. ...  
18. }; 

         这里共有两次转型:第一次用来为 *this添加const(这使接下来调用operator[]时得以调用const版本),第二次则是从constoperator[]的返回值中移除const。

         添加const的那一次转型强迫进行了一次安全转型(将non-const对象转为const对象),所以我们使用static_cast。移除const的那个动作只可以藉由const_cast完成,没有其他选择。

         简单来说就是non-const版本为了调用const版本先转换常量性,应用const版本功能完毕后,为了符合non-const的返回值,再去除常量性。

         const成员函数承诺绝不改变其对象的逻辑状态(logicalstate),non-const成员函数却没有这般承诺。如果在const函数内调用non-const函数,就是冒了这样的风险:你曾经承诺不改动的那个对象被改动了。这就是为什么"const成员函数调用non-const成员函数"是一种错误行为:因为对象有可能因此被改动。

当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值