除了允许变量由const函数修改之外,“ mutable”关键字是否还有其他用途?


527

不久前,我遇到了一些代码,这些代码用mutable关键字标记了一个类的成员变量。据我所知,它只是允许您修改const方法中的变量:

class Foo  
{  
private:  
    mutable bool done_;  
public:  
    void doSomething() const { ...; done_ = true; }  
};

这是此关键字的唯一用途还是它所不及的?此后,我在一个类中使用了此技术,将a标记boost::mutex为可变的,允许const函数出于线程安全的原因将其锁定,但是,老实说,这有点像hack。


2
但是,如果您不进行任何修改,那么为什么要首先使用互斥锁呢?我只想了解这一点。
Misgevolution,

@Misgevolution您正在修改某些东西,您只是在控制谁/如何通过const进行修改。一个非常幼稚的例子,想象一下,如果我只给朋友使用非const句柄,敌人就会得到const句柄。朋友可以修改,敌人不能。
iheanyi

1
注:这里的使用关键字的一个很好的例子mutablestackoverflow.com/questions/15999123/...
加布里埃尔斯台普斯

我希望它可以用于覆盖const(类型),所以我不必这样做: class A_mutable{}; using A = A_mutable const; mutable_t<A> a;如果我想默认使用const,即mutable A a;(显式可变)和A a;(隐式const)。
alfC

Answers:


351

它允许区分按位const和逻辑const。逻辑const是当对象不会以通过公共接口可见的方式更改时(例如您的锁定示例)。另一个示例是一个类,该类在首次请求该值时将其计算出来并缓存结果。

由于mutable可以在lambda上使用c ++ 11 来表示按值捕获的事物是可修改的(默认情况下不是):

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

52
“可变”根本不影响按位/逻辑一致性。C ++ 只是按位const,可以使用'mutable'关键字从此检查中排除成员。除了通过抽象(例如SmartPtrs)之外,不可能在C ++中实现“逻辑”常量。
理查德·科登

111
@Richard:您错过了重点。确实没有“逻辑const”关键字,这是程序员在概念上的区分,它基于对构成对象的逻辑可观察状态的理解来决定通过可变使其排除哪些成员。
托尼·德罗伊

6
@ajay是的,那是markig成员变量可变的全部要点,以允许在const对象中对其进行更改。
KeithB 2013年

6
为什么一个人需要在lambda上可变?通过引用捕获变量是否足够?
Giorgio

11
@Giorgio:区别在于xlambda 中的已修改内容仍保留在lambda中,即lambda函数只能修改其自己的副本x。外面看不到变化,原来x仍然没有变化。考虑将lambda作为函子类实现;捕获的变量对应于成员变量。
塞巴斯蒂安·马赫

138

mutable关键字是刺破的方式const,你悬垂在你的对象面纱。如果您有一个const引用或指向对象的指针,则您不能以任何方式修改该对象,除非何时以及如何对其进行标记mutable

使用您的const参考或指针,您将被限制于:

  • 仅读取任何可见数据成员的访问权限
  • 允许仅调用标记为的方法的权限const

mutable例外使得它,所以你现在可以编写或者被标记的数据集成员mutable。那是唯一的外部可见差异。

在内部const,您可以看到的那些方法也可以写入已标记的数据成员mutable。本质上,const面纱是全面刺穿的。API设计者完全有责任确保mutable它不会破坏const概念,仅在有用的特殊情况下使用。该mutable关键字很有用,因为它清楚地标记了受这些特殊情况约束的数据成员。

在实践中,您可以const在代码库中全神贯注地使用代码(本质上,您想用const“疾病” “感染”您的代码库)。在这个世界上,指针和引用const几乎没有例外,产生的代码更易于推理和理解。对于一个有趣的题外话,请查找“参照透明性”。

如果没有mutable关键字,您最终将不得不使用它const_cast来处理它允许的各种有用的特殊情况(缓存,引用计数,调试数据等)。不幸的const_cast是,相比于mutable强制API 客户端破坏const他正在使用的对象的保护,破坏性要大得多。此外,它还造成了广泛的const破坏:const_castconst指针或引用允许无约束的写操作和对可见成员的方法调用访问。相反,mutable要求API设计人员对const异常进行细粒度的控制,通常这些异常隐藏在const对私有数据进行操作的方法中。

(注意,我多次提到数据和方法的可见性。我说的是标记为公共,私有或受保护的成员,这是这里讨论的完全不同类型的对象保护。)


8
另外,使用const_cast修改const对象的一部分会产生不确定的行为。
布赖恩

我不同意,因为它迫使API客户端破坏对象的const保护。如果要const_cast用于实现const方法中成员变量的变异,则不会要求客户端进行强制转换-您可以通过ing 在方法内进行const_cast强制转换this。基本上,它使您可以绕开特定呼叫站点上任意成员上的mutableconstness ,而让您删除所有呼叫站点上特定成员上的const 。后者通常是您想要的典型用途(缓存,统计信息),但有时const_cast适合该模式。
BeeOnRope

1
const_cast模式在某些情况下确实更适合,例如,当您要临时修改成员然后还原它时(非常像boost::mutex)。该方法在逻辑上是const的,因为最终状态与初始状态相同,但是您要进行瞬时更改。const_cast在那里很有用,因为它可以让您专门mutable删除该方法中的const,否则您的突变将被撤消,但是这样做不合适,因为它将从所有方法中删除const保护,不一定所有方法都遵循“ do” ,撤消”模式。
BeeOnRope17年

2
可能将const 定义的对象放置到只读内存(更一般地,标记为只读的内存)和相关联的标准语言中,这const_cast可能使定时炸弹成为可能。mutable没有此类问题,因为此类对象无法放置在只读存储器中。
BeeOnRope17年

75

您对boost :: mutex的使用正是该关键字的目的。另一个用途是内部结果缓存,以加快访问速度。

基本上,“可变”适用于不影响对象外部可见状态的任何类属性。

在您的问题的示例代码中,如果done_的值影响外部状态,则可变可能是不合适的,这取决于...中的内容。部分。


35

Mutable用于将特定属性标记为可从const方法内部进行修改的属性。那是它的唯一目的。在使用它之前,请仔细考虑,因为如果您更改设计而不是使用代码,您的代码可能会更简洁,可读性更好mutable

http://www.highprogrammer.com/alan/rants/mutable.html

因此,如果上述疯狂不是什么可变的,那是为了什么?这是一个微妙的情况:可变是指对象在逻辑上是恒定的,但实际上需要更改。这些情况很少,而且很遥远。

作者提供的示例包括缓存和临时调试变量。


2
我认为此链接提供了可变的场景的最佳示例,几乎看起来它们仅用于调试。(按正确用法)
狂热爱好者

的使用mutable可以使代码更具可读性和简洁性。在下面的示例中,read可以const像预期的那样。`可变的m_mutex; 容器m_container; void add(Item item){Lockguard lock(m_mutex); m_container.pushback(item); }项目read()const {Lockguard lock(m_mutex); 返回m_container.first(); }`
Th。Thielemann

有一个非常流行的用例:引用计数。
Seva Alekseyev

33

在隐藏了内部状态(例如缓存)的情况下,此功能很有用。例如:

类HashTable
{
...
上市:
    字符串查找(字符串键)const
    {
        if(key == lastKey)
            返回lastValue;

        字符串值= lookupInternal(key);

        lastKey =键;
        lastValue =值;

        返回值
    }

私人的:
    可变字符串lastKey,lastValue;
};

然后,您可以让一个const HashTable对象仍然使用其lookup()方法,该方法会修改内部缓存。


9

mutable 确实存在,正如您推断的那样,它允许人们以其他常量函数修改数据。

目的是您可能具有一个对对象的内部状态“不执行任何操作”的函数const,因此您标记了该函数,但是您可能确实需要以不影响其正确性的方式修改某些对象的状态。功能。

关键字可能是对编译器的提示-理论上的编译器可以在内存中放置一个标记为只读的常量对象(例如全局变量)。的存在mutable暗示,这不应该做的。

这是声明和使用可变数据的一些正当理由:

  • 线程安全。声明a mutable boost::mutex是完全合理的。
  • 统计。给定函数的部分或全部参数,计算对函数的调用次数。
  • 记忆化。计算一些昂贵的答案,然后将其存储以供将来参考,而不必再次重新计算。

2
好的答案,除了关于可变为“提示”的评论。这使得如果编译器将对象放入ROM时,似乎可变成员有时是不可变的。可变的行为已明确定义。
理查德·科登

2
例如,除了将const对象放置在只读存储器中之外,编译器还可以决定优化循环外的const fucntion调用。否则const函数中的可变统计计数器仍将允许这种优化(并且仅计数一个调用),而不是仅仅为了计数更多的调用而阻止优化。
哈根·冯·埃岑

@HagenvonEitzen-我很确定那是不正确的。除非可以证明没有副作用,否则编译器无法将功能提升到循环之外。该证明通常涉及实际检查功能的实现(通常是在内联之后),而不是依赖const(并且无论const或,这种检查都会成功或失败mutable)。仅仅声明函数const是不够的:const函数可以自由地产生副作用,例如修改全局变量或传递给函数的某些东西,因此,这并不是证明的有用保证。
BeeOnRope17年

现在,某些编译器具有特殊的扩展名,例如gcc的_attribute __((const))和__attribute __((pure)),_do 具有这样的效果,但这仅与constC ++中的关键字切线相关。
BeeOnRope17年

8

是的,就是这样。我将其用于由不会在逻辑上更改类状态的方法修改的成员,例如,通过实现缓存来加快查找速度:

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数据不应以任何显着方式改变对象的行为,例如,如果预期写入磁盘的更改将立即对应用程序可见,那么我给出的示例可能会违反此规定。


6

当类中有一个仅在该类中用于表示诸如互斥量或锁之类的信号的变量时,将使用Mutable。该变量不会改变类的行为,但是对于实现类本身的线程安全性是必需的。因此,如果没有“ mutable”,则将无法使用“ const”函数,因为在外部可用的所有函数中都需要更改此变量。因此,为了使成员变量即使是const函数也可写,引入了可变的。

指定的可变项会告知编译器和阅读器这是安全的,并希望可以在const成员函数内修改成员变量。


4

mutable主要用于类的实现细节。类的用户不需要知道它,因此他认为“应该”为const的方法可以。互斥量可变的示例就是一个很好的规范示例。


4

您使用的是不是黑客攻击,但像在C很多事情++,可变可以成为黑客的懒惰的程序员谁不想去所有的背部和标记方式的东西,不应该是const作为非const。


3

对于对用户而言逻辑上是无状态的(因此在公共类的API中应具有“ const”获取器)但在基础IMPLEMENTATION(.cpp中的代码)中不是无状态的情况下,请使用“可变”。

我最经常使用的情况是无状态“普通旧数据”成员的延迟初始化。即,在狭窄的情况下,这种构件的构建(处理器)或随身携带(内存)非常昂贵,并且该对象的许多用户永远都不会要求它们,这是理想的选择。在这种情况下,您需要在后端进行延迟构建以提高性能,因为90%的构建对象根本不需要构建它们,但是您仍然需要提供正确的无状态API供公众使用。


2

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.
}

有关更多详细信息,请参见其他答案,但我想强调一点,它不仅适用于类型安全,而且会影响编译结果。


1

在某些情况下(例如,迭代器设计不佳),该类需要保留一个计数或其他一些偶然的值,这实际上并不会影响该类的主要“状态”。这是我经常看到可变的地方。没有可变性,您将不得不牺牲设计的整个稳定性。

在大多数时候,这对我来说也是一种骇客。在极少数情况下很有用。


1

经典示例(如在其他答案中提到的)以及mutable到目前为止,我所看到的唯一情况是用于缓存复杂Get方法的结果,其中将缓存实现为类的数据成员,而不是实现为方法中的静态变量(出于几个功能之间的共享或纯净度的考虑)。

通常,使用mutable关键字的替代方法通常是方法或const_cast技巧中的静态变量。

另一个详细的解释在这里


1
我从未听说过使用静态成员作为可变成员的一般替代方法。并且const_cast仅当您知道(或已保证)某些内容不会更改(例如,在干扰C库时)或您知道未将其声明为const时。即,修改const-casted const变量会导致未定义的行为。
塞巴斯蒂安·马赫

1
@phresnel“静态变量”是指方法中的静态自动变量(贯穿调用)。并且const_cast可以用来修改方法中的类成员const,这就是我所说的...
Daniel Hershcovich 2013年

1
正如您所写的那样,这对我来说并不十分清楚:)关于直通修改const_cast,正如所说的,仅当未声明对象时才允许这样做const。例如const Frob f; f.something();void something() const { const_cast<int&>(m_foo) = 2;导致不确定的行为。
塞巴斯蒂安·马赫

1

当您覆盖const虚拟函数并想要在该函数中修改子类成员变量时,可变变量会很方便。在大多数情况下,您不想更改基类的接口,因此必须使用自己的可变成员变量。


1

当创建用于类测试目的的存根时,mutable关键字非常有用。您可以对const函数进行存根,但仍然可以增加(可变)计数器或已添加到存根的任何测试功能。这样可以使存根类的接口保持完整。


0

我们使用可变的最好的例子之一是深拷贝。在复制构造函数中,我们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,则编译器将引发错误。


-1

关键字'mutable'实际上是一个保留关键字。通常用于更改常量变量的值。如果要具有多个constsnt值,请使用关键字mutable。

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.