本文内容来源:《C++必知必会》
使用常量成员函数可以改变对象的逻辑状态,虽然对象的物理状态没有发生改变。考虑如下代码,它定义了一个类X:
public :
X():buffer_( 0 ),isComputed_( false ){}
// ...
void setBuffer(){
int * tmp = new int [MAX];
delete [] buffer_;
buffer_ = tmp;
} // setBuffer
void modifyBuffer( int index, int value) const {
buffer_[index] = value; // Valid but not suggested!
} // end of modifyBuffer
int getValue() const {
if ( ! isComputed_){
computedValue = expensiveOperation(); // Error!
isComputed_ = true ; // Error!
}
return computedValue_;
} // end of getValue
private :
static int expensiveOperation();
int * buffer_;
bool isComputed_;
int computeValue_;
}
setBuffer函数必须是非常量的,因为它要修改其所属的X对象的一个数据成员。然而,modifyBuffer可以被合法地被标为常量,因为它没有修改X对象,它只是修改X的buffer_成员所指向的一些数据。这种做法是合法的,但是很不道德。
有时一个被声明为常量的成员函数必须要修改其对象,这常见于利用"lazy evaluation"机制来计算一个值时,换句话说,只有当第一次提出请求,才计算值,目的在于在该请求根本没有发出的其余情形下,让程序运行得更快。在这种情况下会有一个进行转型犯错的诱惑,为的是能够让事情变得更好,即将该成员函数声明为常量。
int ( ! isComputed_){
X * const aThis = const_cast < X * const > ( this );
aThis -> computedValue = expensiveOperation();
aThis -> isComputed_ = true ;
}
return computedValue_;
}
千万抵制住这个诱惑!处理这种情形的正确方式是将有关数据成员声明为mutable:
public :
// ...
int getValue() const {
int ( ! isComputed_){
computedValue = expensiveOperation();
isComputed_ = true ;
}
return computedValue_;
}
private :
mutable bool isComputed_; // 现在可以修改了。
mutable int computedValue_; // 现在可以修改了。
}
类的非静态数据成员可以声明为mutable,这将允许它们的值可以被该类的常量成员函数(当然也包括非常量成员函数)修改,从而允许一个“逻辑上为常量”的成员函数被声明为常量,虽然其实现需要修改该对象。
以下例子可以解释函数重载解析是如何区分一个成员函数的常量和非常量版本的。
public :
// ...
int & operator [] ( int index);
cost int & operator []( int index) const ;
};
int i = 12 ;
X a;
a[ 7 ] = i; // this 是 X *const,因为a是非常量
const X b;
i = b[i]; // this 是 const X *const, 因为b 是常量
考虑如下具有两个常量参数的非成员二元操作符
如果决定声明一个与此重载操作符对应的成员形式的对应物,应该将其声明为常量成员函数,此目的是为了保持左实参的常量性质。
public :
// ...
X operator + ( const X & rightArg); // 左边的参数是非常量
X operator + ( const X & rightArg) const ; // 左边的参数是常量
};