除了允许variables被const函数修改以外,'mutable'关键字还有其他用途吗?
前一段时间我遇到了一些代码,用mutable
关键字标记了一个类的成员variables。 据我可以看到,它只是允许你修改一个const
方法的variables:
class Foo { private: mutable bool done_; public: void doSomething() const { ...; done_ = true; } };
这是这个关键字的唯一用途,还是有更多的比它满足眼睛? 我已经在一个类中使用了这个技术,将一个boost::mutex
标记为mutable,允许const
函数为了线程安全的原因locking它,但是说实话,这感觉就像是一个黑客。
它允许按位const和逻辑const的区分。 逻辑常量是指当一个对象没有通过公共接口可见的方式改变时,就像你的locking例子。 另一个例子是在第一次请求时计算一个值的类,并caching结果。
由于c ++ 11 mutable
可以在lambdaexpression式中使用,表示通过值捕获的东西是可以修改的(它们不是默认的):
int x = 0; auto f1 = [=]() mutable {x = 42;}; // OK auto f2 = [=]() {x = 42;}; // Error: a by-value capture cannot be modified in a non-mutable lambda
mutable
关键字是一种穿透您遮盖物体的const
的方法。 如果您有一个const引用或指向对象的指针, 除了何时以及如何将其标记为mutable
之外 ,不能以任何方式修改该对象。
用你的const
引用或指针,你被限制在:
- 只读取任何可见的数据成员的访问权限
- 只能调用标记为
const
方法的权限。
mutable
exception使得它现在可以写或设置标记为mutable
数据成员。 这是唯一的外部可见的差异。
在内部,你可以看到的const
方法也可以写入标记为mutable
数据成员。 从本质上来说,综合刺破了常规的面纱。 这完全取决于APIdevise者,以确保mutable
不会破坏const
概念,只在有用的特殊情况下使用。 mutable
关键字有帮助,因为它清楚地标记了受这些特殊情况影响的数据成员。
在实践中,你可以在整个代码库中使用const
(你本质上是想用const
“疾病”来感染你的代码库)。 在这个世界中,指针和引用是const
,极less有例外,产生的代码更容易推理和理解。 对于一个有趣的离题,请查阅“参考透明度”。
没有mutable
关键字,你最终将被迫使用const_cast
来处理它允许的各种有用的特殊情况(caching,参考计数,debugging数据等)。 不幸的是, const_cast
比mutable
具有更强的破坏性,因为它迫使API 客户端销毁他正在使用的对象的const
保护。 此外,它会导致广泛的const
破坏: const_cast
一个const指针或引用允许不受限制的写和方法调用访问可见成员。 相比之下, mutable
需要APIdevise者对const
exception进行细粒度的控制,通常这些exception隐藏在对私有数据进行操作的const
方法中。
(注:我指的是数据和方法的几次可见性,我指的是标记为公共或私有或被保护的成员,这是一个完全不同的对象保护types。
你使用boost :: mutex正是这个关键字的目的。 另一个用途是内部结果caching来加速访问。
基本上,“可变”适用于任何不影响对象的外部可见状态的类属性。
在你的问题的示例代码中,如果done_的值影响外部状态,mutable可能不合适,这取决于…中的内容; 部分。
可变是将特定属性标记为const
方法中可修改的。 这是它唯一的目的。 在使用之前要仔细考虑,因为如果你改变devise而不是使用mutable
话,你的代码可能会更干净和更具可读性。
http://www.highprogrammer.com/alan/rants/mutable.html
所以如果上面的疯狂不是可变的,那是什么呢? 下面是一个微妙的例子:mutable是指对象在逻辑上是常量的情况,但实际上需要改变。 这些情况很less,而且存在。
作者给出的例子包括caching和临时debuggingvariables。
在隐藏内部状态(如caching)的情况下,它非常有用。 例如:
HashTable类 { ... 上市: string查找(string键)常量 { 如果(key == lastKey) 返回lastValue; string值= lookupInternal(key); lastKey = key; lastValue = value; 返回值; } 私人的: 可变stringlastKey,lastValue; };
然后你可以有一个const HashTable
对象仍然使用它的lookup()
方法,它修改了内部caching。
那么,是的,就是这样。 我用它来修改那些不会在逻辑上改变类的状态的方法所修改的成员,例如,通过实现一个caching来加速查找:
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
数据不应该以任何重要的方式改变对象的行为,例如,如果预期写入磁盘的改变将立即可见应用程序。
mutable
确实存在,因为您推断允许修改其他常数函数中的数据。
意图是你可能有一个函数“对对象的内部状态”什么都不做“,所以你标记了函数const
,但是你可能真的需要修改一些对象的状态,而不会影响它的状态正确的function。
关键字可以作为编译器的提示 – 一个理论编译器可以在一个标记为只读的内存中放置一个常量对象(如全局对象)。 mutable
提示的存在,这不应该做的。
这里有一些正确的理由来声明和使用可变数据:
- 线程安全。 声明一个
mutable boost::mutex
是完全合理的。 - 统计。 计算一个函数的调用次数,并给出它的部分或全部参数。
- 记忆化。 计算一些昂贵的答案,然后将其存储起来供将来参考,而不是再次重新计算。
mutable主要用于类的实现细节。 类的用户不需要知道它,所以方法的他认为“应该”是const可以的。 你有一个互斥的例子是可变的是一个很好的例子。
你对它的使用并不是黑客行为,尽pipe像C ++中的许多事情一样,mutable对于懒惰的程序员来说是不可行的,因为懒惰的程序员不希望一路走回头路,并且标记不应该是非const的const。
当类内部有一个variables时,就会使用Mutable,该variables只在该类中用于表示例如互斥锁或锁的信号。 这个variables不会改变类的行为,但是为了实现类本身的线程安全是必须的。 因此,如果没有“可变”,你将无法拥有“常量”function,因为这个variables将需要在外部世界可用的所有function中改变。 因此,为了使成员variables甚至通过const函数可写,引入了mutable。
指定的mutable通知编译器和读者它是安全的,并且期望成员variables可以在const成员函数内被修改。
对于逻辑无状态的用户(因此在公共类的API中应该有“const”getters),但在底层IMPLEMENTATION(.cpp中的代码)中不是无状态的时,使用“mutable”。
我最经常使用的情况是无状态的“普通旧数据”成员的延迟初始化。 也就是说,对于构build(处理器)或随身携带(记忆)这样的成员是昂贵的,这种狭义的情况是理想的,并且对象的许多用户将永远不会要求他们。 在这种情况下,你需要在后端进行惰性构build,因为90%的对象根本就不需要构build它们,但是仍然需要为公共消费呈现正确的无状态API。
在一些情况下(比如devise不好的迭代器),类需要保留一个计数或者其他一些附带值,这并不影响类的主要“状态”。 这是最常见的地方,我看到可变使用。 没有可变的,你将被迫牺牲你的devise的整个一致性。
大部分时间对我来说,感觉就像是黑客。 在非常less的情况下有用。
经典的例子(正如其他答案中所提到的)以及我所见过的关于mutable
关键字的唯一情况是用于caching复杂Get
方法的结果,其中caching被实现为类的数据成员,而不是作为该方法中的一个静态variables(出于几个function之间的共享或普通清洁的原因)。
通常,使用mutable
关键字的替代方法通常是方法或const_cast
技巧中的静态variables。
另一个详细的解释是在这里 。
为类testing目的创build存根时,mutable关键字非常有用。 您可以存根const函数,仍然可以增加(可变)计数器或任何testingfunction,你已经添加到您的存根(stub)。 这使被保留的类的接口保持不变。
Mutable将const
的含义从类的const
逐个变为逻辑的const。
这意味着具有可变成员的类更长时间是按位const,并且不会再出现在可执行文件的只读部分。
此外,它通过允许const
成员函数更改可变成员而不使用const_cast
来修改types检查。
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. }
有关更多详细信息,请参阅其他答案,但是我想强调一下,这不仅仅适用于types安全,而且会影响编译结果。
当你重写一个const虚函数并且想要在那个函数中修改你的子类成员variables的时候,mutable可能会很方便。 在大多数情况下,你不想改变基类的接口,所以你必须使用你自己的可变成员variables。
关键字'mutable'实际上是一个保留关键字。常用于改变常量variables的值。如果你想有一个constsnt的多个值,使用关键字mutable。
//Prototype class tag_name{ : : mutable var_name; : : };
我们使用mutable的最好例子之一就是深拷贝。 在复制构造函数中,我们发送const &obj
作为参数。 所以创build的新对象将是常量types。 如果我们想改变(大多数情况下我们不会改变,在极less情况下我们可能会改变),这个新创build的const对象中的成员,我们需要声明它是mutable
。
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
在上面的例子中,我们可以改变成员variablesx
的值,尽pipe它是一个声明为const的对象的一部分。 这是因为variablesx
被声明为可变的。 但是如果你试图修改成员variablesy
的值,编译器会抛出一个错误。