前段时间我遇到了一些用<code>mutable关键字标记类的成员变量的代码。 据我所知,它只是允许你修改const
方法中的变量:
class Foo
{
private:
mutable bool done_;
public:
void doSomething() const { ...; done_ = true; }
};
这是这个关键字的唯一用途,还是有更多的东西比它的眼睛? 我已经在一个类中使用了这个技术,将boost::mutex
标记为可变,允许const
函数为了线程安全原因而锁定它,但是,说实话,它感觉有点像黑客。
#1楼
您对boost :: mutex的使用正是此关键字的用途。 另一个用途是用于内部结果缓存以加快访问速度。
基本上,'mutable'适用于不影响对象外部可见状态的任何类属性。
在您的问题的示例代码中,如果done_的值影响外部状态,则可变性可能不合适,这取决于...中的内容; 部分。
#2楼
Mutable用于将特定属性标记为可在const
方法中修改。 这是它的唯一目的。 在使用之前要仔细考虑,因为如果您更改设计而不是使用mutable
,您的代码可能会更清晰,更易读。
http://www.highprogrammer.com/alan/rants/mutable.html
因此,如果上述疯狂不是可变的,它是什么? 这是一个微妙的案例:mutable是指对象在逻辑上不变的情况,但实际上需要改变。 这些案例很少见,但它们存在。
作者提供的示例包括缓存和临时调试变量。
#3楼
在某些情况下(如设计不佳的迭代器),类需要保留计数或其他偶然值,这不会真正影响类的主要“状态”。 这通常是我看到可变使用的地方。 如果没有变数,你将被迫牺牲整个设计的整体性。
对我来说,大部分时间感觉像是黑客。 在极少数情况下有用。
#4楼
它在您隐藏内部状态(如缓存)的情况下很有用。 例如:
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()
方法,它修改了内部缓存。
#5楼
它允许区分按位const和逻辑const。 逻辑const是指对象不会以通过公共接口可见的方式更改,例如锁定示例。 另一个例子是一个在第一次请求时计算值的类,并缓存结果。
因为可以在lambda上使用c ++ 11 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
#6楼
mutable主要用于类的实现细节。 该类的用户不需要知道它,因此他认为“应该”为const的方法可以。 你的互斥量可变的例子是一个很好的规范例子。
#7楼
嗯,是的,这就是它的作用。 我将它用于通过不在逻辑上改变类状态的方法修改的成员 - 例如,通过实现缓存来加速查找:
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
数据不应该以任何重要方式改变对象的行为,例如,如果预期写入磁盘的更改将立即可见,则我可以违反该示例。应用程序。
#8楼
你对它的使用并不是一种破解,虽然像C ++中的很多东西一样,对于一个懒惰的程序员来说,可变的可能是黑客,他们不想一直回来并且标记一些不应该作为非const的const。
#9楼
当你推断允许一个修改其他常量函数中的数据时,确实存在mutable
。
目的是你可能有一个对对象的内部状态“什么都不做”的函数,所以你标记了函数const
,但你可能真的需要以不影响它的方式修改一些对象状态正确的功能。
关键字可以作为编译器的提示 - 理论编译器可以将一个常量对象(例如全局)放在标记为只读的内存中。 存在可mutable
提示,不应该这样做。
以下是声明和使用可变数据的一些正当理由:
- 线程安全。 声明一个
mutable boost::mutex
是完全合理的。 - 统计。 在给定部分或全部参数的情况下,计算对函数的调用次数。
- 记忆化。 计算一些昂贵的答案,然后将其存储以备将来参考,而不是再次重新计算。
#10楼
如果在类中只有一个变量用于表示诸如互斥锁或锁之类的信号,则使用Mutable。 此变量不会更改类的行为,但是为了实现类本身的线程安全性是必需的。 因此,如果没有“可变”,您将无法拥有“const”函数,因为需要在外部世界可用的所有函数中更改此变量。 因此,引入了mutable,以便使成员变量甚至可以通过const函数进行写入。
指定的mutable通知编译器和读者它是安全的并且期望可以在const成员函数内修改成员变量。
#11楼
对于对用户来说逻辑无状态的事物(因此在公共类'API中应该有“const”getter)时使用“mutable”,但在底层的IMPLEMENTATION(.cpp中的代码)中不是无状态的。
我最常使用的情况是无状态“普通旧数据”成员的惰性初始化。 也就是说,在这种成员构建(处理器)或随身携带(存储器)昂贵的情况下,它是理想的,并且对象的许多用户永远不会要求它们。 在这种情况下,你需要在后端进行延迟构造以提高性能,因为90%的构建对象根本不需要构建它们,但是你仍然需要提供正确的无状态API供公众使用。
#12楼
当覆盖const虚函数并想要修改该函数中的子类成员变量时,mutable可以很方便。 在大多数情况下,您不希望更改基类的接口,因此您必须使用自己的可变成员变量。
#13楼
关键字'mutable'实际上是一个保留的keyword.often它用于改变常量变量的值。如果你想拥有constsnt的多个值,请使用关键字mutable。
//Prototype
class tag_name{
:
:
mutable var_name;
:
:
};
#14楼
该mutable
关键字是刺破的方式const
你披在你的对象面纱。 如果你有一个const引用或指向对象的指针,你不能以任何方式修改该对象, 除非它被标记为mutable
时间和方式。
使用const
引用或指针,您将被限制为:
- 仅对任何可见数据成员的读访问权限
- 只能调用标记为
const
方法的权限。
mutable
异常使得您现在可以编写或设置标记为mutable
数据成员。 这是唯一外部可见的差异。
在内部,您可以看到的那些const
方法也可以写入标记为mutable
数据成员。 从本质上讲,全面穿孔。 完全取决于API设计者,以确保mutable
不会破坏const
概念,并且仅用于有用的特殊情况。 mutable
关键字有帮助,因为它清楚地标记了受这些特殊情况影响的数据成员。
在实践中,您可以在整个代码库中使用const
(您基本上希望用const
“疾病”“感染”您的代码库)。 在这个世界中,指针和引用是const
,只有很少的例外,产生的代码更容易推理和理解。 对于一个有趣的题外话,请查看“参考透明度”。
如果没有mutable
关键字,最终将被迫使用const_cast
来处理它允许的各种有用的特殊情况(缓存,引用计数,调试数据等)。 不幸的是, const_cast
比mutable
更具破坏性,因为它迫使API 客户端破坏他正在使用的对象的const
保护。 此外,它会导致广泛的const
破坏: const_cast
一个const指针或引用允许不受限制的写入和方法调用访问可见成员。 相比之下, mutable
要求API设计者对const
异常进行细粒度控制,通常这些异常隐藏在对私有数据进行操作的const
方法中。
(注:我指的是对数据和方法的可视性几次。我讲的成员标记为公共与私有或保护的是讨论一个完全不同类型的对象的保护这里 。)
#15楼
在为类测试目的创建存根时,mutable关键字非常有用。 您可以存根const函数,但仍然可以增加(可变)计数器或您添加到存根的任何测试功能。 这使得存根类的接口保持不变。
#16楼
Mutable将const
的含义从bitwise 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.
}
请参阅其他答案以获取更多详细信息,但我想强调它不仅仅是针对type-saftey而且它会影响编译结果。
#17楼
我们使用mutable的最好例子之一是深拷贝。 在复制构造函数中,我们发送const &obj
作为参数。 因此创建的新对象将是常量类型。 如果我们想要改变(通常我们不会改变,在极少数情况下我们可能会改变)这个新创建的const对象中的成员我们需要将它声明为mutable
。
mutable
存储类只能用于类的非静态非const数据成员。 即使它是声明为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
的值,编译器将抛出错误。
#18楼
经典的例子(在其他答案中提到)和我见过的迄今为止使用的mutable
关键字的唯一情况是缓存复杂的Get
方法的结果,其中缓存被实现为类的数据成员而不是作为方法中的静态变量(出于在多个函数之间共享或简单清洁的原因)。
通常,使用mutable
关键字的替代方法通常是方法中的静态变量或const_cast
技巧。
另一个详细解释在这里 。