C++中的mutable关键字在这种情况下是否适用?

3
我想问一下在这里使用可变对象是否合适:
#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;
}

基类是第三方库。我正在扩展它以提供自己的NoMod()。该库原始的NoMod()声明为const。
我的NoMod()与基类不同之处在于它需要修改自己的成员变量。
因此,为了使我的NoMod()编译并在调用Draw()时被调用,我必须:
1) 将Derive::NoMod()实现为const 2) 使我的int y可变。
这是我能做的最好的吗?
7个回答

10

很难说,因为您没有提供有关y指的是什么或者其用法的任何上下文。

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


9
正如“头极客”所描述的,对于你的问题的答案取决于数据成员的使用方式。
我将类中的数据成员分为两种类型。
我使用常见术语“属性”来指代数据成员,这些数据成员是对象的逻辑状态或“值”。通常,属性很少被声明为可变的。
我创造了“contribute”这个新词,它表示简单的“工作内存/存储器”数据成员,与对象的状态有所脱离。Contribute对于对象的用户没有上下文相关性,它们只存在于类中以贡献对象的维护和高效运行。Contributes通常在类中被声明为可变的,并且始终是私有的或受保护的。
例如,假设您的对象是一个链接列表,因此您有一个指向列表中第一项的指针。我认为这个指针是一个contribute,因为它不代表列表中的数据。即使列表已排序并且指针设置为列表中的新第一项,列表对象的用户也无需关心列表的维护方式,仅关心列表数据是否已修改,以及列表是否已排序。即使您有一个布尔数据成员'sorted',用于快速确定列表是否处于排序状态,它也将成为一个contribute,因为是列表结构本身赋予了有序状态,'sorted'变量成员仅用于高效地记住状态,而不必扫描列表。
另一个例子是,如果您有一个const方法来搜索列表。假设您知道通常搜索将返回最近先前搜索的项目,则您会在类中保留一个指向这样一个项目的指针,以便您的方法可以首先检查上次找到的项目是否与搜索关键字匹配,然后再搜索整个列表(如果该方法确实需要搜索列表并找到项目,则指针将被更新)。我认为这个指针是一个contribute,因为它只是为了帮助加快搜索而存在。即使搜索更新了contribute指针,在容器中没有任何项的数据被修改,因此该方法有效地是const的。
因此,属性数据成员通常不会被声明为可变的,而对对象功能有所贡献的数据成员通常会是可变的。

哇,这是一个非常好的答案。非常易于理解......现在我完全明白什么是“逻辑”和“物理”的意思了。谢谢你提供这样优秀的答案。 - sivabudh

3

我认为唯一可以使用mutable的情况是对于像引用计数这样并不真正属于对象状态的东西。

如果y是对象的物理状态的一部分,但不是逻辑状态,则可以使用,否则请勿这样做。


1

当你有一个带有'const'成员变量的类,并且需要实现赋值运算符(=)而不跳过'const'成员的赋值时,你可以考虑使用'mutable'。

此外,根据C++标准,对最初声明为'const'的变量应用const_cast并使用它是未定义行为。因此,如果方法的接口接受必须在内部修改的'const',请传递一个'mutable'参数。

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

此致


此外,根据C++标准,const_cast应用于最初声明为“const”的变量是未定义行为。产生未定义行为的不是const_cast本身,而是const_cast去除常量性后对对象进行修改。例如: const int x = 10; cout << const_cast<int>(x) + 1 << endl; // 可以 - Logan Capaldo
是的,你是对的。但我猜为了使用它,人们会丢弃const属性 :-) - Abhay

1

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

我曾经做过另一种不同之处。在你的例子中,你只强制一次更改 const 对象。您还可以使用 const_cast 运算符:

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

当您使用const_cast运算符时,您具有的优势是可以通过在代码中运行操作员名称的搜索轻松识别强制将const转换为非const的地方。
但是,就像我所说的那样,如果该成员不是对象状态的一部分,但必须在多个常量方法中间接更改,请使用mutable。

1

我需要可变特性的唯一情况是:

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

然而,作为一个经验法则,我建议不要经常使用mutable,因为它绕过了C++的精彩const特性。


1
如果像你所说的,这是第三方库的一部分,你可能没有选择。C++ 本质上是一种实用的语言,它让你做你需要做的事情,即使这不总是最佳实践。
但需要注意的一点是,第三方库记录了 NoMod 不应通过添加 const 修饰符来修改对象。违反该合同会让你自己面临潜在的问题。如果库在某些情况下多次调用 NoMod,则你的派生类最好能够处理它,因为真正的 const 方法将不会有任何问题。
我首先会寻找另一种解决问题的方法,但如果失败了,就声明它为 mutable。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接