不久前,我遇到了一些代码,它们用mutable关键字标记了一个类的成员变量。据我所知,它只是允许你在const方法中修改变量:
class Foo
{
private:
mutable bool done_;
public:
void doSomething() const { ...; done_ = true; }
};
这是唯一的使用这个关键字还是有更多的它比满足眼睛?从那以后,我在一个类中使用了这种技术,将boost::mutex标记为mutable,允许const函数出于线程安全的原因锁定它,但是,说实话,这感觉有点hack。
是的,这就是它的作用。我将它用于那些被方法修改的成员,而这些方法在逻辑上不会改变类的状态——例如,通过实现缓存来加速查找:
class CIniWrapper
{
public:
CIniWrapper(LPCTSTR szIniFile);
// non-const: logically modifies the state of the object
void SetValue(LPCTSTR szName, LPCTSTR szValue);
// const: does not logically change the object
LPCTSTR GetValue(LPCTSTR szName, LPCTSTR szDefaultValue) const;
// ...
private:
// cache, avoids going to disk when a named value is retrieved multiple times
// does not logically change the public interface, so declared mutable
// so that it can be used by the const GetValue() method
mutable std::map<string, string> m_mapNameToValue;
};
现在,你必须小心使用它——并发性问题是一个大问题,因为如果只使用const方法,调用者可能会认为它们是线程安全的。当然,修改可变数据不应该以任何重要的方式改变对象的行为,我给出的例子可能违反了这一点,例如,如果期望写入磁盘的更改将立即对应用程序可见。
mutable关键字是一种戳穿对象上的const面纱的方法。如果你有一个指向对象的const引用或指针,你不能以任何方式修改该对象,除非它被标记为可变的。
使用const引用或指针,你被限制为:
仅对任何可见数据成员进行读访问
只调用标记为const的方法的权限。
可变异常使您现在可以编写或设置标记为可变的数据成员。这是唯一能从外部看到的区别。
在内部,那些对你可见的const方法也可以写入标记为可变的数据成员。本质上,const面纱是全面穿透的。完全由API设计者来确保mutable不会破坏const概念,并且只在有用的特殊情况下使用。mutable关键字有帮助,因为它清楚地标记了受这些特殊情况影响的数据成员。
在实践中,您可以在整个代码库中频繁地使用const(实际上您希望用const“疾病”“感染”代码库)。在这个世界中,指针和引用都是const的,只有极少数例外,从而产生的代码更容易推理和理解。一个有趣的题外话,请查阅“参考透明度”。
Without the mutable keyword you will eventually be forced to use const_cast to handle the various useful special cases it allows (caching, ref counting, debug data, etc.). Unfortunately const_cast is significantly more destructive than mutable because it forces the API client to destroy the const protection of the objects (s)he is using. Additionally it causes widespread const destruction: const_casting a const pointer or reference allows unfettered write and method calling access to visible members. In contrast mutable requires the API designer to exercise fine grained control over the const exceptions, and usually these exceptions are hidden in const methods operating on private data.
(注意:我多次提到数据和方法可见性。我说的是标记为public、private或protected的成员,这是这里讨论的完全不同类型的对象保护。)
对于那些对用户来说是逻辑上无状态的东西(因此在公共类的api中应该有“const”getter),但在底层实现(你的.cpp中的代码)中不是无状态的东西,使用“mutable”。
The cases I use it most frequently are lazy initialization of state-less "plain old data" members. Namely, it is ideal in the narrow cases when such members are expensive to either build (processor) or carry around (memory) and many users of the object will never ask for them. In that situation you want lazy construction on the back end for performance, since 90% of the objects built will never need to build them at all, yet you still need to present the correct stateless API for public consumption.
Mutable将const的含义从按位的const更改为类的逻辑const。
这意味着具有可变成员的类将不再是按位的const,并且将不再出现在可执行文件的只读部分中。
此外,它通过允许const成员函数在不使用const_cast的情况下更改可变成员来修改类型检查。
class Logical {
mutable int var;
public:
Logical(): var(0) {}
void set(int x) const { var = x; }
};
class Bitwise {
int var;
public:
Bitwise(): var(0) {}
void set(int x) const {
const_cast<Bitwise*>(this)->var = x;
}
};
const Logical logical; // Not put in read-only.
const Bitwise bitwise; // Likely put in read-only.
int main(void)
{
logical.set(5); // Well defined.
bitwise.set(5); // Undefined.
}
请参阅其他答案了解更多细节,但我想强调的是,这不仅仅是为了类型安全,而且它会影响编译结果。
使用可变的一个最好的例子是,在深度复制中。在复制构造函数中,我们发送const &obj作为参数。因此,新创建的对象将是常量类型。如果我们想要改变这个新创建的const对象中的成员(大多数情况下我们不会改变,在极少数情况下我们可能会改变),我们需要将它声明为mutable。
可变存储类只能用于类的非静态非const数据成员。类的Mutable数据成员可以被修改,即使它是声明为const的对象的一部分。
class Test
{
public:
Test(): x(1), y(1) {};
mutable int x;
int y;
};
int main()
{
const Test object;
object.x = 123;
//object.y = 123;
/*
* The above line if uncommented, will create compilation error.
*/
cout<< "X:"<< object.x << ", Y:" << object.y;
return 0;
}
Output:-
X:123, Y:1
在上面的例子中,我们能够改变成员变量x的值,尽管它是声明为const的对象的一部分。这是因为变量x声明为可变的。但是如果你试图修改成员变量y的值,编译器会抛出一个错误。