1、在类X的非常量成员函数中,this指针的类型为X *const。也就是说,它是指向非常量X的常量指针。由于this指向的对象不是常量,因此它可以被修改。
而在类X的常量成员函数中,this的类型为const X* const。是指向常量X的常量指针。由于指向的对象是常量,因此不能被修改。
这也是为什么可以使用常量成员函数来改变对象的逻辑状态原因,虽然对象的物理状态没有发生改变。
看如下X类的实现,使用一个指向已分配的缓冲区的指针,来保存它的一些状态。
class X
{
public:
X( ) : buffer_(0), isComputered_(false) { }
//.....
void setBuffer()
{
int *tmp = new int [MAX];
delete [ ] buffer_;
buffer_ = tmp;
}
void modifyBuffer( int index, int value) const //不道德
{
buffer_[index] = value;
}
int getValue() const
{
if( !isComputed_ )
{
computedValue = expensiveOperation(); //错误
isComputed_ = true; //错误
}
return computedValue_;
}
private:
static int expensiveOperation( );
int *buffer_;
bool isComputed_;
int computedValue_;
};
setBuffer成员函数必须是非常量的,因为它要修改其所属的x对象的一个数据成员。然而,modifyBuffer可以被合法地标为常量,因为它没有修改x对象,它只是修改X的buffer_成员所指向的一些数据。
2、有时一个真的应该被声明为常量的成员函数必须要修改其对象,这常见于利用“缓存求值”机制来计算一个值时。换句话说,只有当第一次提出请求,才计算值,目的在于在该请求根本没有发出的其余情形下,让程序运行的更快。
函数X::getValue试图对一个代价高昂的计算执行“缓式评估”,但是由于它被声明为常量成员函数,因此不允许它设置对象的isComputed_和computedValue_数据成员的值。在这种情况下会有一个进行转型犯错的诱惑,即将该成员函数声明为常量:
int getValue() const
{
if( !isComputed_ )
{
X *const aThis = const_cast<X *const>(this); //糟糕的念头
aThis->computedValue_ = expensiveOperation();
aThis->isComputed_ = true;
}
return computedValue_;
}
千万不要像上面这样做,正确的做法是将有关数据成员声明为mutable:
class X {
public:
//....
int getValue() const
{
if( !isComputed_ )
{
computedValue_ = expensiveOperation( ); //ok
isComputed_ = true; //ok
}
return computedValue_;
}
private:
//.....
mutable bool isComputed_; //现在可以修改了
mutable int computedValue_; //现在可以修改了
};
3、类的非静态数据成员可以声明为mutable,这将允许它们的值可以被该类的常量成员函数(也包括非常量成员函数)修改,从而允许一个“逻辑上为常量”的成员函数被声明为常量,虽然其实现需要修改该对象。
对成员函数的this指针类型加上常量修饰,就可以解释函数重载解析是如何区分一个成员函数的常量和非常量版本的。下面是一个常见的重载索引操作符的例子:
class X
{
public:
//....
int &operator [ ] (int index);
const int &operator [ ] (int index) const;
//.....
};
4、我们知道二元重载成员操作符的左实参是作为this指针传入的。因此,当对一个X对象进行索引操作时,x对象的地址被作为this指针传入:
int i = 12;
X a;
a[7] = i; //this是X *const,因为a是非常量
const X b;
i = b[i]; //this是const X *const,因为b是常量
重载解析会将常量对象的地址和指向常量的this指针相匹配。
考虑如下具有两个常量参数的非成员二元操作符:
X operator + (const X &, const X &);
如果决定声明一个此重载操作符的对应物,应该将其声明为常量成员函数,目的在于保持左实参的常量性质:
class X
{
public:
//.....
X operator + (const X &rightArg); //左边的参数是非常量
X operator + (const X &rightArg) const; //左边的参数是常量
而在类X的常量成员函数中,this的类型为const X* const。是指向常量X的常量指针。由于指向的对象是常量,因此不能被修改。
这也是为什么可以使用常量成员函数来改变对象的逻辑状态原因,虽然对象的物理状态没有发生改变。
看如下X类的实现,使用一个指向已分配的缓冲区的指针,来保存它的一些状态。
class X
{
public:
X( ) : buffer_(0), isComputered_(false) { }
//.....
void setBuffer()
{
int *tmp = new int [MAX];
delete [ ] buffer_;
buffer_ = tmp;
}
void modifyBuffer( int index, int value) const //不道德
{
buffer_[index] = value;
}
int getValue() const
{
if( !isComputed_ )
{
computedValue = expensiveOperation(); //错误
isComputed_ = true; //错误
}
return computedValue_;
}
private:
static int expensiveOperation( );
int *buffer_;
bool isComputed_;
int computedValue_;
};
setBuffer成员函数必须是非常量的,因为它要修改其所属的x对象的一个数据成员。然而,modifyBuffer可以被合法地标为常量,因为它没有修改x对象,它只是修改X的buffer_成员所指向的一些数据。
2、有时一个真的应该被声明为常量的成员函数必须要修改其对象,这常见于利用“缓存求值”机制来计算一个值时。换句话说,只有当第一次提出请求,才计算值,目的在于在该请求根本没有发出的其余情形下,让程序运行的更快。
函数X::getValue试图对一个代价高昂的计算执行“缓式评估”,但是由于它被声明为常量成员函数,因此不允许它设置对象的isComputed_和computedValue_数据成员的值。在这种情况下会有一个进行转型犯错的诱惑,即将该成员函数声明为常量:
int getValue() const
{
if( !isComputed_ )
{
X *const aThis = const_cast<X *const>(this); //糟糕的念头
aThis->computedValue_ = expensiveOperation();
aThis->isComputed_ = true;
}
return computedValue_;
}
千万不要像上面这样做,正确的做法是将有关数据成员声明为mutable:
class X {
public:
//....
int getValue() const
{
if( !isComputed_ )
{
computedValue_ = expensiveOperation( ); //ok
isComputed_ = true; //ok
}
return computedValue_;
}
private:
//.....
mutable bool isComputed_; //现在可以修改了
mutable int computedValue_; //现在可以修改了
};
3、类的非静态数据成员可以声明为mutable,这将允许它们的值可以被该类的常量成员函数(也包括非常量成员函数)修改,从而允许一个“逻辑上为常量”的成员函数被声明为常量,虽然其实现需要修改该对象。
对成员函数的this指针类型加上常量修饰,就可以解释函数重载解析是如何区分一个成员函数的常量和非常量版本的。下面是一个常见的重载索引操作符的例子:
class X
{
public:
//....
int &operator [ ] (int index);
const int &operator [ ] (int index) const;
//.....
};
4、我们知道二元重载成员操作符的左实参是作为this指针传入的。因此,当对一个X对象进行索引操作时,x对象的地址被作为this指针传入:
int i = 12;
X a;
a[7] = i; //this是X *const,因为a是非常量
const X b;
i = b[i]; //this是const X *const,因为b是常量
重载解析会将常量对象的地址和指向常量的this指针相匹配。
考虑如下具有两个常量参数的非成员二元操作符:
X operator + (const X &, const X &);
如果决定声明一个此重载操作符的对应物,应该将其声明为常量成员函数,目的在于保持左实参的常量性质:
class X
{
public:
//.....
X operator + (const X &rightArg); //左边的参数是非常量
X operator + (const X &rightArg) const; //左边的参数是常量