这两个术语有什么区别,我为什么需要使用mutable
?
"物理"常量性是由声明一个const
对象来实现的,原则上可以通过将对象放置在只读内存中来强制执行它从而防止其更改。试图更改它将导致未定义的行为;它可能会更改、也可能不会、也可能触发保护故障、或者可能熔化内存芯片。
“逻辑”常量性来自于声明一个引用或指针const
,并由编译器强制执行。对象本身可能是“物理”常量,也可能不是,但是引用不可以被使用来修改它,除非进行转换。如果对象不是“物理”常量,那么C++允许你修改它,使用const_cast
绕过保护。
mutable
类成员即使类对象本身(或用于访问它的引用或指针)是const
,也可以被修改。好的使用示例包括必须在读操作期间锁定的互斥量和缓存以存储昂贵读操作的结果。在这两种情况下,操作本身应该是一个const
函数(因为它不影响对象的可见状态),但它需要修改互斥量或缓存,因此它们需要是mutable
。它也可能被滥用,使对象在逻辑上不应该改变时可见地发生变化,因此要谨慎使用;只有声明的成员不属于外部可见状态,则将其声明为mutable
。
当一个对象中存在可以被认为是“内部”的字段时,需要使用mutable
。也就是说,类的任何外部用户都不能确定这些字段的值。虽然实例被视为常量,即不变化,但类可能需要对这些字段进行写操作。
考虑硬盘;它的缓存就是这种状态的一个例子。从实际磁盘读取数据时,会向缓存中写入数据。
在C++中,如果不将相应成员标记为mutable
,则无法清晰地表达这一点,以允许即使在标记了const
的方法中也能够更改它们的值。正如评论中指出的那样,您可以总是拿起锤子并使用const_cast<>
来删除const
属性,但这当然是作弊。 :)
mutable
。那个说法是错误的,因为可以通过强制转换对象或成员来实现(过去已经实现过)。关键字mutable
是在之后引入的,当程序员手动移除对象的constness以获得此效果时,逻辑上的constness的概念才被意识到,使其更易于维护和执行。请参见这里。 - Adrianconst_cast<>
的内容。 - unwind以下是一些代码,供想要尝试物理常量效果的人使用:
#include <type_traits>
using namespace std;
using type = int const;
type &f();
int main()
{
const_cast<remove_const_t<type> &>(f()) = 123;
}
#if defined(__GNUC__) || defined(__llvm__)
__attribute__((noinline))
#elif defined(_MSC_VER)
__declspec(noinline)
#endif
type &f()
{
static type i = 0;
return i;
}
我对Mike Seymour的回答持有不同意见:
一个类的成员函数可以被定义为const函数,这意味着它不会修改对象的任何内部状态(除了那些标记为mutable的成员,在我们后面谈到“逻辑”const-ness时变得很重要)。那就是“物理”const-ness的实际定义。但是即使一个函数被标记为const,它可能在逻辑上修改对象的状态。考虑以下示例:
#include <iostream>
using namespace std;
class Foo
{
private:
int *ip;
public:
Foo(int i) {
ip = new int(i);
}
virtual ~Foo() {
delete ip;
}
void modifyState(int i) const {
*ip = i;
}
int getI(void) const {
return *ip;
}
};
int main(int argc, char *argv[])
{
Foo foo(7);
cout << foo.getI() << endl;
foo.modifyState(9);
cout << foo.getI() << endl;
return 0;
}
输出:
7
9
成员函数modifyState
不会修改成员变量ip
,因此它满足物理const性质的要求。但谁能否认,由于它正在修改ip
指向的内容,逻辑状态并没有发生改变呢?因此,尽管该函数满足了物理const性质的要求,但它不满足逻辑const性质的要求,因为调用modifyState
后getI
函数返回的内容已经被更改。
相反,我们可以在物理上修改一个对象的成员,但该对象在其接口中向外部世界展现的状态并没有改变。考虑以下示例,其中const函数getCelsiusTemperature
和getFahrenheitTemperature
修改了mutable状态,但并未修改对象的逻辑状态:
#include <iostream>
using namespace std;
class Temperature
{
private:
mutable float temperature;
mutable bool is_celsius;
public:
Temperature(double temperature, bool is_celsius=true) {
this->temperature = temperature;
this->is_celsius = is_celsius;
}
double getCelsiusTemperature(void) const {
if (!this->is_celsius) {
// convert to Celsius:
this->temperature = (this->temperature - 32.0) * 5.0 / 9.0;
this->is_celsius = true;
}
return this->temperature;
}
const double getFahrenheitTemperature(void) const {
if (this->is_celsius) {
// convert to Fahrenheit:
this->temperature = this->temperature * 9.0 / 5.0 + 32.0;
this->is_celsius = false;
}
return this->temperature;
}
};
int main(int argc, char *argv[])
{
const Temperature t(100, true);
cout << t.getCelsiusTemperature() << endl;
cout << t.getFahrenheitTemperature() << endl;
cout << t.getCelsiusTemperature() << endl;
return 0;
}
输出:
100
212
100
const
——const
不能保证您的"物理常量性":成员仍可能是mutable
并在运行时更改。因为这是允许的,所以编译器/链接器等默认情况下不会将此类对象放置在只读内存中,即使它们是静态的const
。如果通过某些非标准编译器/链接器/加载器标志强制将它们放置在那里,则程序员必须手动避免使用mutable
成员。您所绘制的对象和指针/引用之间的区别实际上是无关紧要的。 - Tony Delroyconst
之间的差异,而不是一个关于编译器实际执行此操作的声明;他也没有说过编译器会这样做。此外,我不同意他对指针和引用之间区别的看法是无关紧要的。这个答案在概念上是坚实而正确的。在我看来,TonyDelroy在声称这个答案“非常错误”时是不正确的。 - Dan Nissenbaum