3

我想问一下这里使用 mutable 是否合适:

#include <iostream>

class Base
{
protected:
  int x;

public:
  virtual void NoMod() const
  {
    std::cout << x << std::endl;
  }
  void Draw() const
  {
    this->NoMod();  
  }
};

class Derive : public Base
{
private:
  mutable int y;

public:
  void NoMod() const
  {
    y = 5;
  }
};

int main()
{
  Derive derive;

  // Test virtual with derive
  derive.Draw();

  return 0;
}

Base 类是第 3 方库。我正在扩展它以提供我自己的 NoMod()。库原始 NoMod() 被声明为 const。

我的 NoMod() 与 Base 的不同之处在于它需要修改自己的成员变量。

因此,为了让我自己的 NoMod() 编译并在调用 Draw() 时被调用,我必须

1) 将 Derive::NoMod() 实现为 const
2) 使我的 int y 可变。

这是我能做的最好的吗?

4

7 回答 7

10

很难说,因为你没有给出任何关于y指的是什么或如何使用它的上下文。

通常,mutable仅在更改可变变量不会更改对象的实际“值”时才适用。例如,当我为 C 风格的字符串编写包装器时,我需要使内部mLength变量可变,以便我可以缓存长度,即使请求它的对象是const对象。它没有改变长度或字符串,并且在类本身之外不可见,因此mutable可以。

于 2009-04-15T00:41:26.137 回答
9

正如“头极客”所描述的,您的问题的答案取决于您的数据成员的使用方式。

我区分了一个类中的两种类型的数据成员。

我使用通用术语“属性”来指代作为对象的逻辑状态或“值”的数据成员。通常属性很少被声明为可变的。

我创造了原型主义“贡献”,它表示只是“工作内存/存储”的数据成员,并且与对象的状态有些脱节。贡献与对象的用户没有上下文相关性,它们存在于类中只是为了对对象的维护和有效操作做出贡献。贡献通常在类中声明为可变的,并且始终是私有的或受保护的。

例如,假设您的对象是一个链表,因此您有一个指向列表中第一项的指针。我认为这个指针是有贡献的,因为它不代表列表中的数据。即使列表已排序并且指针设置为列表中的新第一项,列表对象的用户也可能不太关心列表的维护方式。只有列表数据是否被修改以及列表是否排序与用户的观点相关。即使您有一个布尔数据成员“排序”来快速确定列表是否处于排序状态,这也将是一个贡献,因为它是列表结构本身赋予排序状态,使用“排序”变量成员只是为了有效地记住状态而不必扫描列表。

再举一个例子,如果你有一个搜索列表的 const 方法。假设您知道通常搜索将返回最近搜索过的项目,您将在类中保留指向此类项目的指针,以便您的方法可以在搜索整个列表之前首先检查最后找到的项目是否与搜索键匹配(如果该方法确实需要搜索列表并找到一个项目,则指针将被更新)。我认为这个指针是一个贡献,因为它只是为了帮助加快搜索速度。即使搜索更新了指针的贡献,该方法实际上是 const 的,因为容器中的项目数据都没有被修改。

因此,作为属性的数据成员通常不被声明为可变的,而有助于对象功能的数据成员通常是可变的。

于 2009-04-16T02:07:28.963 回答
3

我认为唯一mutable没问题的是引用计数之类的事情,这些事情并不是对象状态的一部分。

如果y是对象的物理状态的一部分,但不是逻辑状态,那么这是可以的,否则,不要这样做。

于 2009-04-15T00:34:38.133 回答
1

您可以想到“可变”的另一种情况是当您有一个具有“const”成员变量的类并且您需要实现赋值运算符(=)而不跳过“const”成员的赋值时。

此外,const_cast 应用于最初声明的“const”变量,并根据 C++ 标准使用UB。因此,如果方法的接口接受必须在内部修改的“const”,则将其传递给“可变”参数。

您可以从上述情况判断它的适当性,即只有它在语义上才有意义!不要使用它来使源代码可编译。

问候,

于 2009-04-15T05:29:08.880 回答
1

当类的成员没有真正定义对象的状态时,使用 mutable (例如,有助于提高性能的缓存值/对象)。

我用来做另一个不同的事情。在您的示例中,您只强制对 const 对象进行一次更改。您还可以使用 const_cast 运算符:

const_cast< Derive*>( this)->y = 10;

当您使用 const_cast 运算符时,您可以通过简单地在代码中搜索运算符名称来轻松识别强制执行 const 到非 const 转换的位置。

但是,正如我所说,如果成员不是对象状态的一部分,但必须在几个常量方法中间接更改,则对该成员使用 mutable。

于 2009-04-15T05:39:31.943 回答
1

我需要可变功能的唯一情况是:

  • 派生数据的缓存版本。例如,如果您有一个 Rectangle 类,它有一个可能会被大量调用的 GetSurface() 成员函数,您可以添加一个可变的 m_surfaceCache 成员变量来保留派生数据。
  • 临界区成员变量。这是因为我的 CriticalSection::Enter() 函数在概念上不是 const,但临界区成员变量不是类数据的真实部分,它更像是编译器指南。

但是,作为一般经验法则,我建议不要过于频繁地使用 mutable,因为它绕过了 C++ 美妙的 const 特性。

于 2009-04-15T09:17:28.690 回答
1

如果如您所说,它是第三方库的一部分,您可能别无选择。C++ 本质上是一种实用的语言,可以让你做你需要做的事情,即使它可能并不总是“最佳实践”。

不过要注意的一件事是,第三方库正在记录 NoMod 不应通过添加该 const 说明符来修改对象。通过违反该合同,您会让自己面临可能的麻烦。如果库在某些情况下多次调用 NoMod,您的派生类能够更好地处理它,因为真正的 const 方法不会有问题。

我首先会寻找另一种解决问题的方法,但如果失败则声明它是可变的。

于 2009-04-16T05:26:04.867 回答