除了允许变量被const函数修改之外,'mutable'关键字是否有任何其他用途?

Rob*_*Rob 511 c++ mutable keyword

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

class Foo  
{  
private:  
    mutable bool done_;  
public:  
    void doSomething() const { ...; done_ = true; }  
};
Run Code Online (Sandbox Code Playgroud)

这是这个关键字的唯一用途,还是有更多的东西比它的眼睛?我已经在一个类中使用了这个技术,标记为一个boost::mutex可变的允许const函数为了线程安全的原因锁定它,但是,说实话,这感觉有点像黑客.

Kei*_*thB 341

它允许区分按位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
Run Code Online (Sandbox Code Playgroud)

  • @Richard:你错过了这一点.没有"逻辑const"关键字,是的,相反,它是程序员根据对构成对象的逻辑可观察状态的理解来决定哪些成员应该通过变为可被排除而做出的概念差异. (109认同)
  • 'mutable'根本不影响按位/逻辑常量.C++只**按位const,'mutable'关键字可用于从此检查中排除成员.除了通过抽象(例如SmartPtrs)之外,不可能在C++中实现'逻辑'const. (49认同)
  • @Giorgio:区别在于lambda中修改后的`x`保持在lambda内,即lambda函数只能修改自己的`x`副本.外部看不到变化,原始的`x`仍未改变.考虑将lambdas实现为仿函数类; 捕获的变量对应于成员变量. (10认同)
  • @ajay是的,这就是markig成员变量的可变性,允许在const对象中进行更改. (6认同)
  • 为什么lambda需要变量?通过引用捕获变量是否足够? (6认同)
  • @Giorgio,让我说我有一个烤面包机,我把它放在一个房间里.当人们想要使用烤面包机时,您进入房间并锁上门.完成后,您将解锁门并退出房间(门只能从内部锁定/解锁).假设我进入房间,锁上门,并使用烤面包机.你想用烤面包机.但门被锁了.烤面包机的可观察行为是否改变了?不,烤面包机仍然只能容纳两片面包.仍然将生面包转化为吐司.获得烤面包机不是烤面包机行为的一部分. (4认同)
  • @iheanyi优秀的比喻,巧妙地交付.而且很奇怪,因为有一天,不得不休息一下没有结果的调试 - 后来被证明与线程相关,_no less_-我以为我会放松一些烤土豆烤饼(是的,那就是我'我在谈论).相反,我吹断了断路器.考虑到这次尝试之前的事情,我不知道为什么我会有点意外!我想我的烤面包机也不是线程安全的. (4认同)
  • 我认为@Richard 的评论很有价值,因为它有助于更​​详细地阐明 KeithB 的解释。至少它帮助了我:) (2认同)
  • KeithB和@RichardCorden都是正确的,具体取决于角度。如果KeithB使用的术语得到更严格的定义,就不会有歧义(并且可能不会有分歧);但是在我看来,模棱两可不会干扰KeithB答案的价值-我认为这是有价值的答案,也是正确的答案。 (2认同)
  • @Mushy 使用 `const` 关键字意味着从类的用户的角度来看是常量。如果我将 c1 和 c2 初始化为 `const MyCls c1(); const MyCls c2 = c1;` 那么从这里开始的代码的任何部分,`c1 == c2` 必须始终为 true。由于相等性是由类本身定义的,因此它可以选择使用哪些成员进行相等性检查。因此(例如)如果它决定仅使用公共成员进行此检查,那么它可以使私有变量可变,因为这不会改变对象的常量,并且还可能有助于对象的内部工作。 (2认同)

Dan*_*n L 134

mutable关键字是刺破的方式const,你悬垂在你的对象面纱.如果你有一个const引用或指向对象的指针,你不能以任何方式修改该对象,除非它被标记的时间和方式mutable.

使用您的const引用或指针,您将被限制为:

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

mutable异常使您现在可以编写或设置标记的数据成员mutable.这是唯一外部可见的差异.

在内部const,您可以看到的方法也可以写入标记的数据成员mutable.从本质上讲,全面穿孔.完全取决于API设计者,以确保mutable不破坏const概念,仅用于有用的特殊情况.该mutable关键字的帮助,因为它清楚地标志着数据成员都受到这些特殊情况.

在实践中,您可以const在整个代码库中使用(您基本上希望用const"疾病" "感染"您的代码库).在这个世界中,指针和引用const几乎没有例外,产生的代码更容易推理和理解.对于一个有趣的题外话,请查看"参考透明度".

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

(注:我指的是对数据和方法的可视性几次.我讲的成员标记为公共与私有或保护的是讨论一个完全不同类型的对象的保护这里.)

  • 另外,使用`const_cast`来修改`const`对象的一部分会产生未定义的行为. (8认同)
  • 我不同意_因为它强制 API 客户端破坏对象的 const 保护_。如果您使用 const_cast 在 const 方法中实现成员变量的突变,您不会要求客户端进行强制转换 - 您将在方法内通过 const_cast 执行 this 操作。基本上,它可以让您绕过 **特定调用站点** 上任意成员的常量性,而 `mutable` 让您可以在所有调用站点上删除 **特定成员** 上的 const 性。后者通常是您想要的典型用途(缓存、统计),但有时 const_cast 适合该模式。 (4认同)
  • 可能将 const _defined_ 对象放置到只读内存(更一般地,内存 _marked_ 只读)以及允许这样做的相关标准语言使 `const_cast` 成为可能的定时炸弹。`mutable` 没有这样的问题,因为这样的对象不能放在只读内存中。 (3认同)

Fra*_*rba 75

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

基本上,'mutable'适​​用于不影响对象外部可见状态的任何类属性.

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


Joh*_*kin 35

Mutable用于将特定属性标记为可从const方法内修改.这是它的唯一目的.在使用它之前要仔细考虑,因为如果您更改设计而不是使用,您的代码可能会更清晰,更易读mutable.

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

因此,如果上述疯狂不是可变的,它是什么?这是一个微妙的案例:mutable是指对象在逻辑上不变的情况,但实际上需要改变.这些案例很少见,但它们存在.

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

  • 我认为这个链接提供了一个最好的例子,其中mutable是有用的.几乎看起来它们专门用于调试.(按正确用法) (2认同)

Ada*_*eld 31

它在您隐藏内部状态(如缓存)的情况下很有用.例如:

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()方法,它修改了内部缓存.

  • 这个例子很好,但这种做法隐藏着危险的后果。查看查找调用的人可能会认为它是线程安全的,因为由于 const 限定符,它“不会”更改对象的状态。后来,当事情不起作用时......为了找到竞争条件,几个小时的工作就被浪费了。这是一种可怕的做法。 (4认同)

Llo*_*oyd 9

mutable 当你推断允许一个人修改其他常数函数中的数据时确实存在.

目的是你可能有一个对对象的内部状态"什么也不做"的函数const,所以你标记了这个函数,但是你可能真的需要修改一些对象状态而不影响它的正确功能.

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

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

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

  • 很好的答案,除了关于可变性是"暗示"的评论.这使得如果编译器将对象放入ROM中,似乎可变成员有时不会是可变的.mutable的行为很明确. (2认同)
  • 除了将const对象放在只读存储器中之外,编译器还可以决定例如优化循环中的const fucntion调用.否则const函数中的可变统计计数器仍然允许这样的优化(并且只计算一次调用),而不是仅仅为了计算更多的调用而阻止优化. (2认同)

Sho*_*og9 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;
};
Run Code Online (Sandbox Code Playgroud)

现在,您必须小心使用它 - 并发问题是一个大问题,因为调用者可能会认为如果只使用const方法它们是线程安全的.当然,修改mutable数据不应该以任何重要的方式改变对象的行为,例如,如果预期写入磁盘的更改将立即对应用程序可见,我可能会违反该示例.


mks*_*der 6

如果在类中只有一个变量用于表示诸如互斥锁或锁之类的信号,则使用Mutable.此变量不会更改类的行为,但是为了实现类本身的线程安全性是必需的.因此,如果没有"可变",您将无法拥有"const"函数,因为需要在外部世界可用的所有函数中更改此变量.因此,引入了mutable,以便使成员变量甚至可以通过const函数进行写入.

指定的mutable通知编译器和读者它是安全的并且期望可以在const成员函数内修改成员变量.


Gre*_*ers 5

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


Joh*_*McG 5

您对它的使用不是黑客,尽管就像 C++ 中的许多东西一样,对于不想一直回去并将不应为 const 的东西标记为非常量的懒惰程序员来说,mutable可以是 hack。


小智 5

对于对用户来说逻辑上无状态的事物(因此在公共类的 API 中应该有“const”getter)但在底层 IMPLEMENTATION(您的 .cpp 中的代码)中不是无状态的,请使用“可变”。

我最常使用它的情况是无状态“纯旧数据”成员的延迟初始化。也就是说,当这些成员的构建(处理器)或携带(内存)成本很高并且对象的许多用户永远不会要求它们时,它是理想的。在这种情况下,您需要在后端进行延迟构建以提高性能,因为 90% 的构建对象根本不需要构建它们,但您仍然需要提供正确的无状态 API 以供公共使用。


Kev*_*Cox 5

constMutable 将类的含义从按位 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.
}
Run Code Online (Sandbox Code Playgroud)

有关更多详细信息,请参阅其他答案,但我想强调它不仅仅是为了类型安全,而且它会影响编译结果。