'const'方法可以改变什么?

What can a 'const' method change?

本文关键字:什么 改变 方法 const      更新时间:2023-10-16

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改变成员变量,但它确实会改变数组的内容。

"常量"方法可以改变什么?

在不显式抛弃恒常性的情况下,const成员函数可以更改:

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

这些限制也适用于数据成员和基础的操作(在 OO 意义上(。 更明确地说,在*this的数据成员或基上运行的const成员函数,当它们为class/struct/union类型时,只能调用其const成员函数(如果有(,并且只能写入其mutable数据成员(如果有(。

(const成员函数还可以更改任何非const局部变量和按值参数,但我知道这不是您感兴趣的(。

const数据成员可以调用其他const成员函数,这些函数将具有这些相同的功能和限制。

例如,如果实例变量是指针,这是否意味着指针没有改变,或者它们指向的内存没有改变?

这意味着指针不能(容易/意外(更改。 这并不意味着指向的内存无法更改。

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

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

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

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

这个问题有两个方面:

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

问题1

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

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

问题2

当编译器未验证时,const如何应用? 它意味着它对您的应用程序意味着什么。 这通常被称为逻辑常量。 何时使用const逻辑常性是有争议的。

const 应用于方法时意味着:

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

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

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

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

例如

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;};

解决了问题。