8.4.3 const对象和成员函数

可以用const限定类成员函数,这是什么意思呢?为了搞清楚这一点,必须首先掌握const对象的概念。

用户定义类型和内建类型一样,都可以定义一个const对象。例如:

8.4.3 const对象和成员函数 - 图1

这里,b是类型blob的一个const对象。它的构造函数被调用,且其参数为“2”。由于编译器强调对象为const的,因此它必须保证对象的数据成员在其生命期内不被改变。它可以很容易地保证公有数据不被改变,但是它怎么知道哪些成员函数将会改变数据?它又如何知道哪些成员函数对于const对象来说是“安全”的呢?

如果声明一个成员函数为const,则等于告诉编译器该成员函数可以为一个const对象所调用。一个没有被明确声明为const的成员函数被看成是将要修改对象中数据成员的函数,而且编译器不允许它为一个const对象所调用。

然而,不能到此为止。仅仅声明一个函数在类定义里是const的,还不能保证成员函数按声明的方式去做,所以编译器强迫程序员在定义函数时要重申const说明。(const已成为函数识别符的一部分,所以编译器和连接程序都要检查const。)为确保函数定义的常量性,如果我们改变对象中的任何成员或调用一个非const成员函数,编译器就将发出一个出错信息,这样,可以保证声明为const的任何成员函数能够按定义方式运行。

要理解声明const成员函数的语法,首先注意前面的带const的函数声明,它表示函数的返回值是const,但这不会产生想要的结果。相反,必须把修饰符const放在函数参数表的后面,例如:

8.4.3 const对象和成员函数 - 图2

8.4.3 const对象和成员函数 - 图3

关键字const必须用同样的方式重复出现在定义里,否则编译器把它看成一个不同的函数,因为f()是一个const成员函数,所以不管它试图以何种方式改变i或者调用另一个非const成员函数,编译器都把它标记成一个错误。

一个const成员函数调用const和非const对象是安全的,因此,可以把它看做成员函数的最一般形式(不幸的是,成员函数并不会自动地默认为const)。不修改数据成员的任何函数都应该把它们声明为const,这样它可以和const对象一起使用。

下面是一个比较const和非const成员函数的例子:

8.4.3 const对象和成员函数 - 图4

8.4.3 const对象和成员函数 - 图5

构造函数和析构函数都不是const成员函数,因为它们在初始化和清除时,总是对对象作些修改。quote()成员函数也不能是const函数,因为它要修改数据成员lastquote(请看return语句)。而lastQuote()没做修改,所以它可以成为const函数,而且也可以被const对象cq安全地调用。

8.4.3.1 可变的:按位const和按逻辑const

如果想要建立一个const成员函数,但仍然想在对象里改变某些数据,这时该怎么办呢?这关系到按位(bitwise)const和按逻辑(logical)const(有时也称为按成员(memberwise)const)的区别。按位const意思是对象中的每个字节都是固定的,所以对象的每个位映像从不改变。按逻辑const意思是,虽然整个对象从概念上讲是不变的,但是可以以成员为单位改变。当编译器被告知一个对象是const对象时,它将绝对保护这个对象按位的常量性。要实现按逻辑const的属性,有两种由内部const成员函数改变数据成员的方法。

第一种方法已成为过去,称为“强制转换常量性(casting away constness)”。它以相当奇怪的方式执行。取this(这个关键字产生当前对象的地址)并把强制转换成指向当前类型对象的指针。看来this已经是所需的指针,但是,在const成员函数内部,它实际上是一个const指针,所以,还应把它强制转换成一个普通指针,这样就可以在那个运算中去掉常量性。下面是一个例子:

8.4.3 const对象和成员函数 - 图6

8.4.3 const对象和成员函数 - 图7

这种方法是可行的,在过去的程序代码里可以看到这种用法,但这不是首选的技术。问题是:常量性的缺乏隐藏在成员函数的定义中,并且没有来自类接口的线索知道对象的数据实际上被修改,除非用户不能见到源代码(用户必然怀疑常量性被转换了,并寻找这一类型转换)。为了公开这一切,应当在类声明里使用关键字mutable,以指定一个特定的数据成员可以在一个const对象里被改变。

8.4.3 const对象和成员函数 - 图8

现在,类用户可从声明里看到哪个成员能够用const成员函数进行修改。

8.4.3.2 只读存储能力

如果一个对象被定义成const对象,它就成为被放进只读存储器(ROM)中的候选者,这经常是嵌入式系统程序设计中要考虑做的重要事情。然而,只建立一个const对象是不够的—只读存储能力所需要的条件要严格得多。当然,这个对象还应是按位const的,而不是按逻辑const的。如果只通过关键字mutable实现按逻辑常量化的话,就容易看出这一点。如果在一个const成员函数里的const被强制转换了,编译器可能检测不到这种情况。另外:

1)class或struct必须没有用户定义的构造函数或析构函数。

2)这里不能有基类(将在第14章中谈到),也不能包含有用户定义构造函数或析构函数的成员对象。

在只读存储能力类型的const对象中的任何部分上,有关写操作的影响没有定义。虽然适当形成式的对象可被放进ROM里,但是目前还没有什么对象需要放进ROM里。