设为首页 加入收藏

TOP

条款03:尽可能使用const(1)
2013-10-07 13:18:29 来源: 作者: 【 】 浏览:74
Tags:条款 尽可能 使用 const

条款03:尽可能使用const(1)

Use const whenever possible.

const的一件奇妙事情是,它允许你指定一个语义约束(也就是指定一个"不该被改动"的对象),而编译器会强制实施这项约束。它允许你告诉编译器和其他程序员某值应该保持不变。只要这(某值保持不变)是事实,你就该确实说出来,因为说出来可以获得编译器的襄助,确保这条约束不被违反。

关键字const多才多艺。你可以用它在classes外部修饰global或namespace(见条款2)作用域中的常量,或修饰文件、函数、或区块作用域(block scope)中被声明为static的对象。你也可以用它修饰classes内部的static和non-static成员变量。面对指针,你也可以指出指针自身、指针所指物,或两者都(或都不)是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语法虽然变化多端,但并不莫测高深。如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。

如果被指物是常量,有些程序员会将关键字const写在类型之前,有些人会把它写在类型之后、星号之前。两种写法的意义相同,所以下列两个函数接受的参数类型是一样的:

  1. void f1(const Widget* pw);      //f1获得一个指针,指向一个  
  2. //常量的(不变的)Widget对象.  
  3. 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 =//iter的作用像个T* const  
  4. vec.begin( );  
  5. *iter = 10;                     //没问题,改变iter所指物  
  6. ++iter;                         //错误!iter是const  
  7. std::vector<int>::const_iterator cIter =//cIter的作用像个const T*  
  8. vec.begin( );  
  9. *cIter = 10;                        //错误! *cIter是const  
  10. ++cIter;                        //没问题,改变cIter。 

const最具威力的用法是面对函数声明时的应用。在一个函数声明式内,const可以和函数返回值、各参数、函数自身(如果是成员函数)产生关联。

令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。举个例子,考虑有理数(rational numbers,详见条款24)的operator* 声明式:

  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

我不知道为什么会有人想对两个数值的乘积再做一次赋值(assignment),但我知道许多程序员会在无意识中那么做,只因为单纯的打字错误(以及一个可被隐式转换为bool的类型):

  1. if (a * b = c) ...//喔欧,其实是想做一个比较(comparison)动作! 

如果a和b都是内置类型,这样的代码直截了当就是不合法。而一个"良好的用户自定义类型"的特征是它们避免无端地与内置类型不兼容(见条款18),因此允许对两值乘积做赋值动作也就没什么意思了。将operator* 的回传值声明为const可以预防那个"没意思的赋值动作",这就是该那么做的原因。

至于const参数,没有什么特别新颖的观念,它们不过就像local const对象一样,你应该在必要使用它们的时候使用它们。除非你有需要改动参数或local对象,否则请将它们声明为const。只不过多打6个字符,却可以省下恼人的错误,像是"想要键入 '==' 却意外键成 '=' "的错误,一如稍早所述。

const成员函数

将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。这一类成员函数之所以重要,基于两个理由。第一,它们使class接口比较容易被理解。这是因为,得知哪个函数可以改动对象内容而哪个函数不行,很是重要。第二,它们使"操作const对象"成为可能。这对编写高效代码是个关键,因为如条款20所言,改善C++(www.cppentry.com) 程序效率的一个根本办法是以pass by reference-to-const方式传递对象,而此技术可行的前提是,我们有const成员函数可用来处理取得(并经修饰而成)的const对象。

许多人漠视一件事实:两个成员函数如果只是常量性(constness)不同,可以被重载。这实在是一个重要的C++(www.cppentry.com) 特性。考虑以下class,用来表现一大块文字:

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

附带一提,真实程序中const对象大多用于passed by pointer-to-const 或passed by reference-to-const的传递结果。上述的ctb例子太过造作,下面这个比较真实:

  1. void print(const TextBlock& ctb) //此函数中ctb是const  
  2. {  
  3. std::cout << ctb[0];    //调用const TextBlock::operator[]  
  4. ...  

只要重载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[] 调用动作自身没问题。错误起因于企图对一个"由const版之operator[] 返回"的const char& 施行赋值动作。

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

  1. tb[0] = 'x'; 

那是因为,如果函数的返回类型是个内置类型,那么改动函数返回值从来就不合法。纵使合法,C++(www.cppentry.com)以by value返回对象这一事实(见条款20)意味被改动的其实是tb.text[0]的一个副本,不是tb.text[0]自身,那不会是你想要的行为。

让我们为哲学思辨喊一次暂停。成员函数如果是const意味什么?这有两个流行概念:bitwise constness(又称physical constness)和logical constness。

bitwise const阵营的人相信,成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是const。也就是说它不更改对象内的任何一个bit。这种论点的好处是很容易侦测违反点:编译器只需寻找成员变量的赋值动作即可。bitwise constness正是C++(www.cppentry.com) 对常量性(constness)的定义,因此const成员函数不可以更改对象内任何non-static成员变量。

不幸的是许多成员函数虽然不十足具备const性质却能通过bitwise测试。更具体地说,一个更改了"指针所指物"的成员函数虽然不能算是const,但如果只有指针(而非其所指物)隶属于对象,那么称此函数为bitwise const不会引发编译器异议。这导致反直观结果。假设我们有一个TextBlock-like class,它将数据存储为char* 而不是string,因为它需要和一个不认识string对象的C API沟通:

  1. class CTextBlock {  
  2. public:  
  3. ...  
  4. char& operator[](std::size_t position) const // bitwise const声明,  
  5. { return pText[position]; }          //  但其实不适当.  
  6. private:  
  7. char* pText;  
  8. };  

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇条款03:尽可能使用const(2) 下一篇用Windows组件库文件快速部署Visu..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: