偶然有这种情况,一个成员函数在逻辑上是const,但它却仍需要改变某个成员的值。对于用户而言,这个函数看似没有改变其对象的状态,然而,它却可能更新了某些用户不能直接访问的细节。这通常被称为逻辑的常量性。例如,Date类可能有一个函数,它应返回一个用户可以用于打印的字符串表示。构造出这种表示可能是一个相对费时的操作,因此,保留一个副本,在重复需要的时候直接返回这个副本,这一做法也就有意义了,除非这个Date值被改变。

class Date {
public:
	string stringRep() const;
	//...
private:
	bool cacheValid;
	string cache;
	void computeCacheValue();
	//...
};

从用户的角度看,stringRep并没有改变它的Date的状态,所以它应该是个const成员函数。而在另一方面,在使用之前必须填充缓存,要做到这一点只能通过蛮力:

string Date::stringRep() const
{
	if (!cacheValid)
	{
		Date *th = const_cast<Date *>(this);
		th->computeCacheValue();
		th->cacheValid = true;
	}
	return cache;
}

也就是说,这里用const_cast运算符从this获得一个Date *指针。这当然一点也不优美,而且它也无法保证总能工作,例如,当被应用的对象原本就是一个const的时候。例如:

Date d1;
const Date d2;
string s1 = d1.stringRep();
string s2 = d2.stringRep(); //undefined

对于d1的情况,stringRep()简单地将其强制转回原来的类型,所以这个调用能够完成。然而,d2原本定义为const,具体实现很有可能为保护它的值不被破环而使用某种特殊形式存储。因此,无法保证d2.stringRep()能在所有实现上都给出同样的可预见的结果。

显式类型转换“强制去掉const”,以及由它引起的依赖于实现的行为还是可以避免的,只要将缓存管理所涉及的数据声明为mutable:

class Date {
public:
	string stringRep() const;
	//...
private:
	mutable bool cacheValid;
	mutable string cache;
	void computeCacheValue() const;
	//...
};

存储描述符mutable特别说明这个成员需要以一种能允许更新的方式存储——即使它是某个const对象的成员。换言之,mutable意味着“不可能是const”。这种机制可用于简化stringRep()的定义。

string Date::stringRep() const
{
	if (!cacheValid)
	{
		computeCacheValue();
		cacheValid = true;
	}
	return cache;
}

这就使stringRep()的合理使用都能合法化了。例如,

Date d1;
const Date d2;
string s1 = d1.stringRep();
string s2 = d2.stringRep(); //ok

如果在某个表示中(只有)一部分允许改变,将这些成员声明为mutable是最合适的。如果一个对象在逻辑上保持为const的同时,其中的大部分需要修改,那么最好将这些需要修改的数据放入另一个独立的对象里,并间接地访问它。假设采用这种技术,使用缓存的字符串将变成

struct Cache
{
	bool valid;
	string rep;
};

calss Date
{
public:
	//...
	string stringRep() const; //字符串表示
private:
	Cache *c; //在构造函数里初始化
	void computeCacheValid() const; //填充引用的缓存
	//...
};

string Date::stringRep() const
{
	if (!c->valid)
	{
		computeCacheValid();
		c->valid = true;
	}
	return c->rep;
}

也可以将

Cache *c;

改为

Cache &c;

但是不能改为

Cache c;

因为如果是指针或引用,Date对象中包含的属性是类似Cache对象的地址,虽然改变了Cache对象的状态,但是其地址并没有改变,所以对于Date对象而言,其状态并没有改变。但是如果Date对象直接包含一个Cache对象的话,Cache对象状态的改变间接改变了Date对象的状态。

posted on 2010-03-22 20:04  cppfans  阅读(433)  评论(0编辑  收藏  举报