【问题标题】:What can a 'const' method change?'const' 方法可以改变什么?
【发布时间】:2011-07-28 00:06:37
【问题描述】:

C++ 方法允许使用const 限定符来指示该方法未更改对象。但是,这是什么意思?例如。如果实例变量是指针,是指指针没有改变,还是表示它们指向的内存没有改变?

具体来说,这是一个最小的示例类

class myclass {
  int * data;

  myclass() {
    data = new int[10];
  }

  ~myclass() {
    delete [] data;
  }

  void set(const int index) const {
    data[index] = 1;
  }
};

方法set 是否正确地限定为const?它不会改变成员变量data,但确实会改变数组的内容。

【问题讨论】:

  • 那么指向指针的指针呢?和指向指针的指针?等等……
  • 您忘记了复制构造函数和赋值运算符
  • @Nemo:代码实际上是一个最小的示例。我没有声称它有任何用处。

标签: c++


【解决方案1】:

“const”方法可以改变什么?

在不显式抛弃常量的情况下,const 方法可以改变:

  • mutable 数据成员,以及
  • 类具有非const 访问权限的任何数据,无论该数据是否可访问:
    • 通过指针或引用的成员变量,
    • 通过作为函数参数传递的指针或引用,
    • 通过函数返回的指针或引用,
    • 直接在包含它的命名空间或类(用于静态)中。

对于class/struct/union 类型的成员,它依赖于其成员函数的常量性来确定应该允许哪些操作。 (它还可以更改任何非 const 局部变量和按值参数,但我知道这不是您感兴趣的)。

它可以调用其他具有相同能力和限制的const方法。

例如。如果实例变量是指针,是指指针没有改变,还是表示它们指向的内存没有改变?

这意味着指针不能(容易/意外)更改。它确实 not 意味着指向的内存无法更改。

您偶然发现的是 const 函数更改概念上由对象拥有的指向或引用数据的逻辑错误。正如您所发现的,编译器不会强制执行您可能想要或期望的const 正确性。这有点危险,但意味着 不需要为指向其他对象的指针/引用显式删除常量,这些对象可能会作为const 函数的副作用而更改。例如,日志记录对象。 (通常,此类对象在逻辑上不属于 const 函数对其进行操作的对象。)关键是编译器无法可靠地区分对象对指向数据的逻辑所有权类型,所以它必须以一种或另一种方式猜测并允许程序员覆盖或不受const-ness 的保护。 C++ 放弃了保护。

有趣的是,我听说 Walter Bright 的 D 语言会翻转这个默认值,在 const 函数中默认生成指向数据 const。这对我来说似乎更安全,尽管很难想象最终需要明确抛弃 constness 以允许想要的副作用的频率,以及这是否会让人感到令人满意的精确或令人讨厌的冗长。

【讨论】:

  • +1,为什么没有upvote? (至少这个答案讨论了关于mutable 的许多其他人都缺少的内容)。
  • @iammilind:感谢您的支持......我在其他几个小时后添加了我的答案 - 我认为兴趣已经“冲过”这个问题,但我有一些我想要的角度表达。干杯,托尼
  • 有时,这也取决于提问者。我总是在回答时看到提问者的声誉;初学者(少于 200 次代表)通常没有耐心/成熟度来阅读冗长的答案,这是一种趋势;他们总是在寻找short and sweet。您的回答涵盖了问题的许多方面,但不幸的是,OP 似乎并不真正想要那个):)
  • @iammilind:您对我的推理的推测是正确的:我确实选择了最简洁、正确的答案作为“接受”的答案。在我的帖子之后,Kerrek 很快就写好了。它完全回答了我需要继续编程的问题。但我非常感谢您提供的额外细节和背景!我忘记了 mutable 关键字(我自己从来没有用过;我很久没学会了),明确提到“副作用”这个词很好。
  • 这对我很有帮助。谢谢。
【解决方案2】:

最简洁的意思是,this 的类型在 const 成员函数中是 const T *,其中 T 是你的类,而在非限定函数中它是 T *

你的方法set不会改变data,所以它可以被限定为const。换句话说,myclass::data 被作为this->data 访问并且是int * const 类型。

【讨论】:

  • 是的,这对编译器意味着什么。但是会用 const 作为他的例子吗?
  • I 不会使用 C 数组! :-)
  • @Kerrek:当然,除非你实现了std::vector ;-)
  • @Andre:确实如此,但即便如此,由于需要重新分配,我也无法通过写访问器 const 进行操作! :-)
  • André: std::vector 内部实际上并没有使用array-of-T,因为它想将内存分配与构造分开......
【解决方案3】:

这个问题有两个方面:

  1. const 对编译器意味着什么?
  2. const 在编译器无法验证时如何应用?

问题 1

第一个相当简单。编译器验证没有数据成员被修改(除非它们被限定为mutable)。它递归地验证这一点:对于任何用户定义的类型,它检查是否没有调用非常量方法。对于内置类型,它会验证它们是否未被分配。

指针的转换是T*T*const(常量指针),而不是const T*(常量指针)。这意味着编译器不会验证指向的对象是否未被修改。显然,这就引出了问题2。

问题 2

const 在未经编译器验证时如何应用?它意味着它对您的应用程序应该意味着什么。这通常被称为逻辑常量。何时使用 const 相对于逻辑常量是 subjectdebate

【讨论】:

  • Kerrek 和 André 答案的差异让我一时感到困惑。但两者都是正确的:this 的类型是const 成员函数内部的const T *(指向常量的指针),其中T 表示类。并且任何类型的成员变量U 变为U const。因此,如果U 是指针类型V *,则它变为V * const(常量指针)。 André 使用了与 Kerrek 相同的字母 T,但它们的含义不同。
【解决方案4】:

const 应用于方法时意味着:

这意味着对象的state不会被方法改变。
这意味着作为对象状态一部分的任何成员都不能被修改,也不能调用任何不是 const 的函数。

因为这与指针有关。这意味着指针(如果它是状态的一部分)不能更改。但是指针指向的对象是另一个对象的一部分,这意味着你可以在这个对象上调用非成本方法(因为它不是这个对象状态的一部分)。

【讨论】:

  • 嗨,马丁。我认为这将有助于区分编译器强制执行的内容和最佳实践。例如,“对象的状态不会被方法改变”。这是使用 const 的最佳实践,但不是编译器要求:您可以制作可观察状态 mutable,您可以抛弃 constness....
  • @Tony:任何可变的东西都不是对象状态的一部分。可变成员应该保存可以从状态计算的缓存计算。考虑它的一种简单方法是,如果您持久化成员(在存储时),它是状态的一部分,可变成员很少持久化。抛弃 constness 是出现问题的第一个迹象(有一些很好的例外(像往常一样))。
  • "Anything that is not mutable..." 有效地重申了您的答案,但仍然没有说这是最佳实践,而不是编译器强制执行的。但是,希望我们的 cmets 的组合能让读者满意。重新“缓存计算” - 其他用途,如统计/仪器和锁定。前者你可能想要也可能不想要坚持,但我同意坚持是一个很好的指标。无论如何,干杯。
【解决方案5】:

const 基本上可以防止在函数内部更改类实例成员的值。这对于更清晰的界面很有用,但在使用继承时会造成限制。正如您发布的示例中那样,有时会有点欺骗(或实际上很多)。

const 最适合Get 函数,很明显调用者正在读取一个值并且无意更改对象状态。在这种情况下,您还需要限制继承的实现以遵守constness,以避免在使用多态性时出现混淆和隐藏的错误。

例如

class A{
     int i;
   public:
     virtual int GetI() {return i;};
}

class B : public A{
   public:
     int GetI() { i = i*2; return i;}; // undesirable
}

将 A 更改为:

virtual int GetI() const {return i;};

解决问题。

【讨论】:

  • 我没有(也不会)投反对票,但恕我直言,这是关于const 通用性的一个很好的观点,但完全忽略了问题的主旨 - 这是关于修改指出的-to / 来自 const 成员的引用数据....
  • 我并不是想在这里变得困难 - 只是为了解释为什么我认为有人可能会投反对票。 “防止更改函数内部的类实例成员的值”并没有具体解决指向/引用的数据,从程序员的逻辑角度来看,这些数据可能被认为是由实例拥有的(因此是成员的值)松散的感觉)。它当然没有解决为什么 C++ 可能决定允许通过 const 函数中的指针/引用访问非const ......在我看来,这个问题再次提出......
  • 您的示例具有误导性,B 无法访问i,因为它是A 的私人成员。
猜你喜欢
  • 1970-01-01
  • 2020-03-12
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 2011-11-29
  • 1970-01-01
  • 2020-02-09
  • 2012-06-09
相关资源
最近更新 更多