提问人:Rob 提问时间:9/20/2008 最后编辑:Jan SchultkeRob 更新时间:6/14/2023 访问量:282096
“可变”关键字除了允许 const 成员函数修改数据成员之外,还有其他用途吗?
Does the 'mutable' keyword have any purpose other than allowing a data member to be modified by a const member function?
问:
不久前,我遇到了一些使用关键字标记类的数据成员的代码。据我所知,它只是允许您在 -qualified 成员方法中修改成员:mutable
const
class Foo
{
private:
mutable bool done_;
public:
void doSomething() const { ...; done_ = true; }
};
这是这个关键词的唯一用途,还是它比眼前看到的要多?从那以后,我在一个类中使用了这种技术,将 标记为 ,允许函数出于线程安全原因锁定它,但老实说,这感觉有点像黑客。boost::mutex
mutable
const
答:
您与 boost::mutex 的使用正是此关键字的用途。另一个用途是内部结果缓存以加快访问速度。
基本上,“可变”适用于任何不影响对象外部可见状态的类属性。
在您问题的示例代码中,如果 done_ 的值影响外部状态,则 mutable 可能不合适,这取决于 ...;部分。
可变用于将特定属性标记为可从方法中修改。这是它的唯一目的。在使用它之前请仔细考虑,因为如果您更改设计而不是使用 ,您的代码可能会更干净、更易读。const
mutable
http://www.highprogrammer.com/alan/rants/mutable.html
因此,如果上述疯狂不是什么 可变是干什么用的,它干什么用?这是 微妙的情况:可变的用于 对象在逻辑上是 常量,但在实践中需要 改变。这些案例很少,而且很遥远 之间,但它们存在。
作者给出的示例包括缓存和临时调试变量。
评论
mutable
read
const
在某些情况下(例如设计不佳的迭代器),类需要保留计数或其他一些附带值,这不会真正影响类的主要“状态”。这是我最常看到可变使用的地方。如果没有可变性,您将被迫牺牲设计的整体稳定性。
大多数时候对我来说,这感觉也像是一个黑客。在极少数情况下有用。
在具有隐藏内部状态(如缓存)的情况下,它很有用。例如:
class HashTable { ... public: string lookup(string key) const { if(key == lastKey) return lastValue; string value = lookupInternal(key); lastKey = key; lastValue = value; return value; } private: mutable string lastKey, lastValue; };
然后,您可以让对象仍然使用其方法,该方法会修改内部缓存。const HashTable
lookup()
评论
It allows the differentiation of bitwise const and logical const. Logical const is when an object doesn't change in a way that is visible through the public interface, like your locking example. Another example would be a class that computes a value the first time it is requested, and caches the result.
Since c++11 can be used on a lambda to denote that things captured by value are modifiable (they aren't by default):mutable
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
评论
x
x
x
可变主要用于类的实现细节。类的用户不需要知道它,因此他认为“应该”是常量的方法。你让互斥锁可变的例子就是一个很好的规范例子。
嗯,是的,这就是它的作用。我将其用于由不逻辑更改类状态的方法修改的成员 - 例如,通过实现缓存来加快查找速度:
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
你对它的使用不是一个黑客,尽管就像C++中的许多东西一样,可变对于一个懒惰的程序员来说可能是黑客,他们不想一路回过头来,把不应该被const的东西标记为non-const。
mutable
确实存在,因为您推断允许人们修改其他常量函数中的数据。
其目的是,您可能有一个对对象的内部状态“不执行任何操作”的函数,因此您标记了该函数,但您可能确实需要以不影响其正确功能的方式修改某些对象状态。const
关键字可以作为编译器的提示 -- 理论编译器可以在内存中放置一个标记为只读的常量对象(例如全局对象)。存在不应这样做的提示。mutable
以下是声明和使用可变数据的一些正当理由:
- 螺纹安全。声明 a 是完全合理的。
mutable boost::mutex
- 统计学。计算对函数的部分或全部参数的调用次数。
- 记忆。计算一些昂贵的答案,然后将其存储以备将来参考,而不是再次重新计算。
评论
const
const
mutable
const
const
const
关键字是一种刺穿您披在物体上的面纱的方法。如果具有指向某个对象的常量引用或指针,则无法以任何方式修改该对象,除非标记该对象的时间和方式。mutable
const
mutable
使用您的引用或指针,您只能:const
- 仅对任何可见数据成员进行读取访问
- 仅调用标记为 的方法的权限。
const
例外情况使得您现在可以写入或设置标记为 的数据成员。这是唯一外部可见的差异。mutable
mutable
在内部,那些对您可见的方法也可以写入标记为 的数据成员。从本质上讲,const 面纱被全面刺穿。这完全取决于 API 设计人员,以确保它不会破坏概念,并且仅在有用的特殊情况下使用。关键字很有帮助,因为它清楚地标记了受这些特殊情况影响的数据成员。const
mutable
mutable
const
mutable
在实践中,你可以在整个代码库中痴迷地使用(你本质上是想用“疾病”“感染”你的代码库)。在这个世界上,指针和引用只有极少数例外,产生的代码更容易推理和理解。对于一个有趣的题外话,请查找“参考透明度”。const
const
const
如果没有关键字,您最终将被迫使用它来处理它允许的各种有用的特殊情况(缓存、引用计数、调试数据等)。不幸的是,它比它迫使 API 客户端破坏他正在使用的对象的保护更具破坏性。此外,它还会导致广泛的破坏:ing const 指针或引用允许不受限制的写入和方法调用访问可见成员。相反,API 设计人员需要对异常进行细粒度控制,并且通常这些异常隐藏在对私有数据进行操作的方法中。mutable
const_cast
const_cast
mutable
const
const
const_cast
mutable
const
const
(注:我多次提到数据和方法可见性。我说的是标记为公共与私有或受保护的成员,这是此处讨论的完全不同类型的对象保护。
评论
const_cast
const
const_cast
const
const_cast
this
mutable
const_cast
boost::mutex
const_cast
mutable
const_cast
mutable
经典的例子(如其他答案中提到的)也是我迄今为止看到的关键字使用的唯一情况,是用于缓存复杂方法的结果,其中缓存被实现为类的数据成员,而不是方法中的静态变量(出于多个函数之间的共享或简单清洁的原因)。mutable
Get
通常,使用关键字的替代方法通常是方法或技巧中的静态变量。mutable
const_cast
另一个详细的解释在这里。
评论
const_cast
const_cast
const
const_cast
const
const Frob f; f.something();
void something() const { const_cast<int&>(m_foo) = 2;
当类中有一个变量时,使用 Mutable,该变量仅在该类中用于表示互斥锁或锁等内容。此变量不会改变类的行为,但对于实现类本身的线程安全是必需的。因此,如果没有“可变”,您将无法拥有“const”函数,因为该变量需要在外部世界可用的所有函数中更改。因此,引入了可变变量,以使成员变量即使由常量函数也可写。
指定的可变对象通知编译器和读取器,它 是安全的,并且预计成员变量可以在常量中修改 member 函数。
对于对用户来说在逻辑上无状态(因此在公共类的 API 中应该有“const”getter)但在底层 IMPLEMENTATION(.cpp中的代码)中不是无状态的东西,请使用 “mutable”。
我最常使用的情况是无状态“纯旧数据”成员的延迟初始化。也就是说,在狭窄的情况下,当这些成员的构建(处理器)或随身携带(内存)都很昂贵,并且对象的许多用户永远不会要求它们时,它是理想的。在这种情况下,您希望在后端进行延迟构造以提高性能,因为 90% 构建的对象根本不需要构建它们,但您仍然需要提供正确的无状态 API 供公共使用。
当您重写 const 虚拟函数并希望修改该函数中的子类成员变量时,可变对象会很方便。在大多数情况下,您不希望更改基类的接口,因此您必须使用自己的可变成员变量。
关键字“可变”实际上是一个保留关键字,通常用于改变常量变量的值。如果要具有 constsnt 的多个值,请使用关键字 mutable。
//Prototype
class tag_name{
:
:
mutable var_name;
:
:
};
在为类测试目的创建存根时,mutable 关键字非常有用。您可以存根 const 函数,并且仍然能够增加(可变)计数器或添加到存根的任何测试功能。这样可以使存根类的接口保持不变。
Mutable 将类的含义从按位 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.
}
有关更多详细信息,请参阅其他答案,但我想强调的是,它不仅适用于类型安全,还会影响编译结果。
我们使用 mutable 的最好例子之一是,在深拷贝中,在 copy 构造函数中,我们作为参数发送。因此,创建的新对象将具有常量类型。如果我们想更改(大多数情况下我们不会更改,在极少数情况下我们可能会更改)这个新创建的 const 对象中的成员,我们需要将其声明为 。const &obj
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
在上面的例子中,我们能够更改成员变量的值,尽管它是声明为 const 的对象的一部分。这是因为变量被声明为可变的。但是,如果您尝试修改成员变量的值,编译器将抛出错误。x
x
y
评论
mutable
const
class A_mutable{}; using A = A_mutable const; mutable_t<A> a;
mutable A a;
A a;