概要:

const成员函数基本,意味着什么?

如果const成员函数内部需要修改成员变量?

当两个函数功能相同,只是用作const与non-const对象,代码重复量很大时怎么办?

const成员函数需要调用non-const成员函数怎么办?

众所周知的是,const对象只能调用const成员函数。

比如:

  1: #include <iostream>
  2: #include <string>
  3: 
  4: class TextBlock {
  5: 
  6: public:
  7: 	TextBlock(char* str) : text(str) {}
  8: 	const char& operator[](std::size_t position) const //operator[] for
  9: 	{ std::cout << "const operator[]" << std::endl; return text[position]; }//const object
 10: 	char& operator[](std::size_t position)//operator[] for
 11: 	{ std::cout << "non-const operator[]" << std::endl; return text[position]; }//non-const object
 12: 
 13: private:
 14: 	std::string text;
 15: };
 16: 
 17: int main()
 18: {
 19: 	TextBlock tb("Hello");
 20: 	std::cout << tb[0] << std::endl;//调用non-const TextBlock::operator[]
 21: 
 22: 	const TextBlock ctb("Hello");
 23: 	std::cout << ctb[0] << std::endl;//调用const TextBlock::operator[]
 24: 
 25: 	return 0;
 26: }
 27: 

程序很简单,结果就不贴了。

我们知道这样的语句

ctb[0]=’x’是错误的,编译器会提醒你:

error: assignment of read-only location ‘ctb.TextBlock::operator[](0u)’

不过编译器是否一直表达的是你想表达的意思呢?

再看一个例子:

  1: #include <iostream>
  2: 
  3: using namespace std;
  4: 
  5: class TextBlock {
  6: 
  7: public:
  8: 
  9: TextBlock(char* str,int n) {
 10: 
 11: pText = new char[n+1];
 12: 
 13: for ( int i=0; i<n; ++i) pText[i]=str[i];
 14: 
 15: }
 16: 
 17: char& operator[](std::size_t index) const {
 18: 
 19: return pText[index];
 20: 
 21: }
 22: 
 23: char* pText;
 24: 
 25: };
 26: 
 27: int main()
 28: 
 29: {
 30: 
 31: const TextBlock t("Hello",6);
 32: 
 33: printf("%s\n",t.pText);
 34: 
 35: t[0]='U';
 36: 
 37: printf("%s\n",t.pText);
 38: 
 39: return 0;
 40: 
 41: }
 42: 
 43: 

该程序的输出:

Hello

Uello

刚看到程序的时候,我想象的结果的确不是这个样子的。不过后来一想就明白了,这的确不是编译器的错误。

让我们先来看下编译器是怎么去“想”的。

成员函数如果是const意味着什么?在编译器看来:bitwise constness(or physical constness)。

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

对我们刚才程序而言,pText被修改了么?没有!指针没有修改!但似乎,很有可能跟我们预想的类的功能不一样。

再考虑一种需要在const成员函数内需要改变成员变量的情况:

  1: class CTextBlock {
  2: 
  3: public:
  4: 
  5: ...
  6: 
  7: std::size_t length() const;
  8: 
  9: private:
 10: 
 11: char* pText;
 12: 
 13: std::size_t textLength; //最近一次计算的文本区块长度
 14: 
 15: bool lengthIsValid; //目前的长度是否有效
 16: 
 17: };
 18: 
 19: std::size_t CTextBlock::length() const
 20: 
 21: {
 22: 
 23: if (!lengthIsValid) {
 24: 
 25: textLength = std::strlen(pText);//错误!在const成员函数内
 26: 
 27: lengthIsVaild = true;//不能赋值给textLength和lengthIsValid
 28: 
 29: }
 30: 
 31: return textLength;
 32: 
 33: }

length的实现当然不是bitwise const,因为textLength和lengthIsValid都可能被修改。这两笔数据被修改对const CTextBlock对象而言虽然可接受,但编译器不同意。它们坚持bitwise constness,怎么办?

解决办法很简单:利用C++的一个与const相关的摆动场:mutable(可变的)。mutable释放掉non-static成员变量的bitwise constness约束。

mutable std::size_t textLength;

mutable bool lengthIsValid;

接下来看最后两个问题(其实是一个)。

在上面的例子里,假设operator[]前需要一堆判断条件,我们需要同时放进const和non-const operator[]中

  1: const char& operator[](std::size_t pos) const
  2: {
  3: … //边界检验(bounds checking)
  4: … //志记数据访问(log access data)
  5: … //检验数据完整性(verify data integrity)
  6: return text[pos];
  7: }
  8: 
  9: char& operator[](std::size_t pos) 
 10: {
 11: … //边界检验(bounds checking)
 12: … //志记数据访问(log access data)
 13: … //检验数据完整性(verify data integrity)
 14: return text[pos];
 15: 
 16: }

随着编译时间、维护、代码膨胀的问题就越来越明显。

解决办法之一是将重复的部分移到另一个成员函数(往往是一个private)

但我们也可以使一个调用另一个,这促使我们将常量性移除(casting away constness)。

需要时,我们一般在non-const成员函数里调用const成员函数。

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

{ return const_cast<char&>(static_cast<const TextBlock&>(*this)[operation]); }

如你所见,有两次转型:

1. 我们打算让non-const operator[]调用其const兄弟,因此需要明确转型加上const防止递归调用。

2. 从const operator[]的返回值中移除const。

为什么不让const版本调用non-const版本呢?

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

下面的程序使const成员函数调用了non-const成员函数,但同时const对象的成员变量被修改了。

  1: #include <iostream>
  2: using namespace std;
  3: 
  4: class TextBlock {
  5: public:
  6: TextBlock(size_t len) : m_len(len){}
  7: const size_t getLen() { return m_len; }
  8: void setLen(size_t len) { m_len = len; }
  9: void testFunction() const 
 10: { 
 11: const_cast<TextBlock&>(*this).setLen(1); 
 12: }
 13: 
 14: size_t m_len;
 15: };
 16: 
 17: int main()
 18: {
 19: const TextBlock t = 2;
 20: cout << t.m_len << endl;
 21: t.testFunction();
 22: cout << t.m_len << endl;
 23: return 0;
 24: }