天天看点

重读经典-《Effective C++》Item3:尽可能使用const

本博客(http://blog.csdn.net/livelylittlefish )贴出作者(三二一@小鱼)相关研究、学习内容所做的笔记,欢迎广大朋友指正!

1. const关键字

const可以修饰的对象分类

  • 在class外部修饰global或者namespace(reference Item2)作用域中的常量
  • 在class内部修饰static和non-static成员变量
  • 在文件、函数、区块作用域(block scope)中修饰被声明为static的对象
  • 对于指针,可以修饰指针本省、指针所指物、或者同时修饰两者
  • 修饰函数返回值,函数参数,成员函数本身

如,

char greeting[] = "Hello";

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

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

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

Const char* const p = greeting;  //const pointer, const data

判断规则:

  • 如果const出现在*左边,表示被指物是常量
  • 如果const出现在*右边,表示指针本省是常量
  • 如果const出现在*两边,表示被指物和指针本身都是常量

另外,

void f1(const Widget* pw);  //f1获得一个指针,指向一个不变的Widget对象

void f2(Widget const * pw); //f2 is the same as f1

2. STL迭代器与const

STL迭代器的作用像T*指针

声明迭代器为const就像声明指针为const一样,即声明一个T* const指针,表明迭代器不能指向不同的东西,但所指的东西的值可变;

若希望迭代器所指的东西不可改变,即希望STL模拟一个const T*指针,则需要const_iterator;

如:

std::vector<int> vec;

...

const std::vector<int>::iterator iter = vec.begin();  //iter即T* const指针,指针本身不可变

*iter = 10;  //ok

++iter;      //error, because iter is const

Std::vector<int>::const_iterator citer = vec.begin(); //citer即const T*指针,指针所指物不可变

*citer = 10; //error, because *citer is const

++citer;     //ok

3. 函数与const

(1) 修饰函数的返回值

另函数返回一个常量值,可以降低因客户错误造成的意外。

如:

class Rational{...};

const Rational operator*(const Rational& lhs, const Rational& rhs);

Rational a, b, c;

...

(a * b) = c;  //在a*b的成果上调用operator=,对函数的返回值在进行赋值无意义

              //如果a,b都是内置类型,编译不通过

              //此处编译也不通过,因为operator*的返回值为const,不可变,对其进行赋值当然不允许

if (a * b = c)//实际上是'==',却意外写成了'=',如果operator*函数的返回值声明为const,这类错误即可在编译期间被发现

如果函数的返回类型是个内置类型,那么改动函数返回值从来就不合法。

(2) const参数

防止在函数内对该参数进行修改。如想要键入'==',却意外键成'=',这类的错误会在编译期间被发现。

(3) const成员函数

两个成员函数如果只是常量性(constness)不同,可以被重载。

小结:

  • Const object only can call const function member, can not call non-const function member.
  • Non-const object can call either const or non-const function member.
  • 在const成员函数中,不能修改成员变量;若一定要修改,可将要修改的成员变量加上mutable属性。

Source code

#include <iostream>

#include <string>

using namespace std;

class TextBlock

{

public:

    TextBlock(): text("")

     {

     }

    TextBlock(const char t[]): text(t)

     {

     }

    TextBlock(const TextBlock& tb): text(tb.text)

    {

    }

    ~TextBlock(){}

    const char& operator[](std::size_t position) const

    {

        return text[position];

    }

    char& operator[](std::size_t position)

    {

        return text[position];

    }

private:

    std::string text;

};

int main(int argc, char* argv[])

{

    TextBlock tb("Hello");

    std::cout << tb[0];     //call non-const TextBlock::operator[]

    const TextBlock ctb("World");

    std::cout << ctb[0] << endl;   //call const TextBlock::operator[]

    return 0;

}

debug

# gdb ./item3

GNU gdb Red Hat Linux (6.6-35.fc8rh)

Copyright (C) 2006 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB.  Type "show warranty" for details.

This GDB was configured as "i386-redhat-linux-gnu"...

Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) b 44

Breakpoint 1 at 0x8048898: file item3.cpp, line 44.

(gdb) r

Starting program: /home/zubo/effectivecpp/item3

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ac/2eeb206486bb7315d6ac4cd64de0cb50838ff6.debug

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/88/27308433e33aeefb560f42fb133577c8936f20.debug

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/92/8ab51a53627c59877a85dd9afecc1619ca866c.debug

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/db/8cb95645d5df469d4aece301cdb5e60087be21.debug

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ba/4ea1118691c826426e9410cafb798f25cefad5.debug

Breakpoint 1, main () at item3.cpp:44

44          TextBlock tb("Hello");

(gdb) step

TextBlock (this=0xbfd566cc, t=0x8048b00 "Hello") at item3.cpp:18

18          TextBlock(const char t[]): text(t)

(gdb)

20           }

(gdb)

20           }

(gdb)

main () at item3.cpp:45

45          std::cout << tb[0];     //call non-const TextBlock::operator[]

(gdb)

TextBlock::operator[] (this=0xbfd566cc, position=0) at item3.cpp:35

35              return text[position];

(gdb)

36          }

(gdb)

main () at item3.cpp:47

47          const TextBlock ctb("World");

(gdb)

TextBlock (this=0xbfd566c8, t=0x8048b06 "World") at item3.cpp:18

18          TextBlock(const char t[]): text(t)

(gdb)

20           }

(gdb)

20           }

(gdb)

main () at item3.cpp:48

48          std::cout << ctb[0] << endl;   //call const TextBlock::operator[]

(gdb)

TextBlock::operator[] (this=0xbfd566c8, position=0) at item3.cpp:30

30              return text[position];

(gdb) set print pretty

(gdb) p *this

$1 = {

  text = {

    static npos = 4294967295,

    _M_dataplus = {

      <std::allocator<char>> = {

        <__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>},

      members of std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Alloc_hider:

      _M_p = 0x85c602c "World"

    }

  }

}

(gdb) c

Continuing.

HW

Program exited normally.

(gdb) q

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

两个流派:

  • Bitwise constness, 又称physical constness

观点:成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是const,即不更改对象内的任何一个bit。

Bitwise constness正是C++对常量性的定义,因此const成员函数不可以更改对象内任何non-static成员变量。

  • Logical constness

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

例如,operator[]函数,

你真正应该做的是实现operator[]的机能一次而使用它两次。即必须令其中一个调用另一个。这就是常量性转除(casting away constness)。

本例中const operator[]完全做掉了non-const版本应该做的一切,唯一的不同就是其返回类型多了一个const资格修饰,这种情况下如果将返回值的const转除是安全的,因为不能谁调用non-const operator[],都一定首先有个non-const对象,否则不能调用non-const对象。所以令non-const operator[]调用其const兄弟是一个避免代码重复的安全做法——即使过程中需要一个转型动作。

小结:

  • 运用const成员函数实现其non-const版本(即non-const版本调用const版本)来避免两者间的重复
  • 不应该用const成员函数调用non-const版本来避免重复(因为const成员函数不改变对象的逻辑状态(其实就是不改变其成员变量),而non-const函数没有这样的承诺)
  • const函数调用non-const函数,有可能改变对象(因为non-const函数可以对对象做任何动作)
  • non-const函数调用const函数不会有这样的危险

4. 总结

const可以用在

  • 指针和迭代器上
  • 指针、迭代器和reference所指的对象上
  • 函数参数和返回类型上
  • local变量上
  • 成员变量、成员函数上

Remember

  • 将某些东西声明为const可以帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
  • 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)。
  • 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

继续阅读