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

时间:2020-03-06 14:27:36  来源:igfitidea点击:

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

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

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

解决方案

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

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

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

Mutable用于在const方法中将特定属性标记为可修改。那是它的唯一目的。在使用它之前请仔细考虑,因为如果我们更改设计而不是使用mutable,代码可能会更清晰,可读性更好。

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

So if the above madness isn't what
  mutable is for, what is it for? Here's
  the subtle case: mutable is for the
  case where an object is logically
  constant, but in practice needs to
  change. These cases are few and far
  between, but they exist.

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

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

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

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

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和逻辑const。逻辑const是指对象不会以通过公共接口(例如锁定示例)可见的方式更改时。另一个示例是一个类,该类在首次请求该值时会计算该值并缓存结果。

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

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的方法可以。具有互斥量可变的互斥示例是一个很好的规范示例。

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

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方法,它们是线程安全的。当然,修改"可变"数据不应以任何显着方式更改对象的行为,例如,如果预期写入磁盘的更改将立即可见,那么我给出的示例可能会违反该行为到应用程序。

我们对它的使用不是黑客,尽管像C ++中的许多事情一样,可变的对于那些不想一直回到原来并标记不应为const的东西的懒惰程序员来说可能是黑客。

当我们推断允许一个人以其他常量函数修改数据时,确实存在"可变"。

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

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

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

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

关键字mutable是一种刺穿我们覆盖在对象上的const面纱的方法。如果我们有一个const引用或者指向对象的指针,则不能以任何方式修改该对象,除非何时以及如何将其标记为"可变"。

使用const引用或者指针,我们将不得不:

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

mutable例外使我们可以编写或者设置标记为mutable的数据成员。那是唯一的外部可见差异。

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

实际上,我们可以在代码库中全神贯注地使用const(实际上,我们想用const"疾病"来"感染"代码库)。在这个世界中,指针和引用是const,只有很少的例外,产生的代码更易于推理和理解。对于一个有趣的题外话,请查找"参照透明性"。

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

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