一个const成员函数可以改变哪些内容?

35
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,但它确实改变了数组的内容。

1
指向指针的情况怎么样?指向指针的指针呢?指向指针的指针的指针呢?以此类推... - user142019
3
你忘记了拷贝构造函数和赋值运算符。 - Nemo
4
@Nemo:这段代码只是一个最简单的示例。我并不声称它有任何实际用途。 - Daniel
5个回答

38
一个 'const' 方法能改变什么? 除非显式地取消 const 限定,否则一个 const 成员函数可以更改: - mutable 数据成员,并且 - 类能够访问的任何数据,无论该数据是否可访问: - 通过成员变量是指针或引用, - 通过作为函数参数传递的指针或引用, - 通过函数返回的指针或引用, - 直接在包含它的命名空间或类(对于静态成员)中。
这些限制也适用于数据成员和基础类(在OO意义上)的操作。更明确地说,当 const 成员函数在此数据成员或基础类上运行时,如果它们是 class/struct/union 类型,则只能调用它们的 const 成员函数(如果有),并且只能写入其 mutable 数据成员(如果有)。
(一个 const 成员函数还可以更改任何非 const 的局部变量和按值传递的参数,但我知道这不是你感兴趣的。)
const 数据成员可以调用其他 const 成员函数,这些函数将具有相同的能力和限制。
例如,如果实例变量是指针,这是否意味着指针未被更改,或者指向它们的内存也未被更改? 这意味着指针不能轻易/意外更改。 这并不意味着指向的内存不能更改。你所遇到的问题是一个const函数会改变对象拥有的指向或引用数据的逻辑不正确。正如你所发现的,编译器在这里并没有强制实施const正确性。这有点危险,但意味着常量性不需要明确地删除指向其他可能因常量函数而产生副作用而更改的对象的指针/引用。例如,日志记录对象。(通常,这些对象并不是逻辑上由const函数操作的对象“拥有”的。)关键点是编译器无法可靠地区分对象对指向数据的逻辑所有权的类型,因此它必须猜测一种方式,并允许程序员覆盖或不受到const保护。C++放弃了保护。
有趣的是,我听说Walter Bright的D语言将此默认值翻转,使指向的数据在const函数中默认为const。这对我来说似乎更加安全,尽管很难想象有多少次需要显式地弃用constness以允许所需的副作用,以及这是否感觉令人满意精确或让人讨厌冗长。

+1,为什么没有点赞?(至少这个答案讨论了“可变”的问题,而其他很多答案都没有提到)。 - iammilind
@iammilind:感谢支持......我在其他人回答几个小时后才添加了我的答案-我认为兴趣已经“涌过”这个问题,但我想表达一些自己的看法。干杯,Tony - Tony Delroy
有时候,这也取决于提问者。我在回答问题时总是会看到提问者的声誉;初学者(少于200分)通常没有耐心或成熟度去仔细阅读冗长的答案;他们总是寻求“简短而甜美”的回答。你的回答涵盖了问题的许多方面,但不幸的是,似乎OP并不真正想要那样的回答 :) - iammilind
@iammilind:你对我的推理是正确的:我确实选择了最简洁、正确的答案作为“被接受的”答案。而Kerrek在我的帖子后很快就写出了它。它完全回答了我需要继续编程的问题。但是,我很感激你提供的额外细节和背景!我已经忘记了mutable关键字(从未使用过它;自学以来已经很长时间了),明确提到“副作用”这个词是很好的。 - Daniel

20

简而言之,这意味着在const成员函数中,this的类型为const T *,其中T是你的类,在未经限定的函数中,它是T *

您的方法set不会更改data,因此可以被限定为const。换句话说,myclass::data被访问为this->data,其类型为int * const


1
是的,这是编译器的意思。但是您会在他的示例中使用const吗? - Karoly Horvath
4
我不会使用 C 数组! :-) - Kerrek SB
@Kerrek:当然,除非你正在实现std::vector;-) - André Caron
@Andre:确实如此,但即使这样,由于需要重新分配内存,我也无法将我的写访问器设置为const! :-) - Kerrek SB
3
André说:std::vector内部并没有实际使用数组类型T[],因为它想将内存分配与构造函数分离开来... - Nemo

7
这个问题有两个方面:
1. 对于编译器,const 是什么意思? 2. 当编译器无法验证 const 时,const 如何应用?
问题1:
首先,编译器会验证没有数据成员被修改(除非它们被声明为 mutable)。对于任何用户定义的类型,它会递归地检查是否调用了任何非const的方法。对于内置类型,它会验证它们没有被赋值。
指针的转换是将 T* 转换为 T*const (const 指针),而不是 const T*(指向常量的指针)。这意味着编译器不会验证指向的对象是否被修改。显然,这导致了问题2。
问题2:
当编译器未进行验证时,如何应用const?这意味着它对您的应用程序应该具有的含义。这通常被称为逻辑const。在何时使用const与逻辑上的const有关,这是一个话题,需要辩论

1
我曾经因Kerrek和André的答案不同而感到短暂的困惑。但两者都是正确的:在const成员函数内,this的类型为const T *(指向常量的指针),其中T表示类。任何类型U的成员变量都变为U const。因此,如果U是指针类型V *,它将变为V * const(常量指针)。André使用了与Kerrek相同的字母T,但它们的含义不同。 - Daniel

0

const 应用于方法时的意思是:

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

关于指针,这意味着指针(如果它是状态的一部分)不能被更改。但指针所指向的对象是另一个对象的一部分,因此可以在该对象上调用非 const 方法(因为它不是该对象的状态的一部分)。


嗨,马丁。我认为区分编译器强制执行和最佳实践会有所帮助。例如,“该方法不会改变对象的状态”。这是使用const的最佳实践,但不是编译器的要求:您可以使可观察状态成为mutable,您可以去除constness - Tony Delroy
@Tony:任何可变的东西都不是对象状态的一部分。可变成员应该保存可以从状态计算出来的缓存计算结果。一个简单的思考方式是,如果你在持久化时保留了成员(存储),那么它就是状态的一部分,而可变成员很少被持久化。去除const属性通常是出现问题的第一个迹象(通常有一些好的例外情况)。 - Martin York
“任何不可变的东西…”有效地重申了您的答案,仍然没有说这是最佳实践而不是编译器强制执行的。但是,希望我们评论的结合能够满足读者。关于“缓存计算”-还有其他用途,比如统计/仪表和锁定。前者可能或可能不想要持久化,但我同意持久性是一个很好的指标。无论如何,干杯。 - Tony Delroy

-1

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

解决了问题。


我没有(也不会)给它点踩,但在我看来,这是关于const的通用性的一个好观点,但完全忽略了问题的重点——即如何从一个const成员修改指向/引用的数据... - Tony Delroy
我并不是想刁难,只是想解释一下为什么我认为有人可能会投反对票。"防止在函数内更改类实例成员的值"并没有明确涉及指向/引用数据,从程序员的逻辑角度来看,这些数据可能被认为是由实例拥有的(因此在宽松意义上可以视为成员的值)。它当然也没有解决为什么C++可能决定允许在const函数内通过指针/引用进行非const访问... 这似乎是问题所在... - Tony Delroy
你的例子有误导性,B无法访问i,因为它是A的私有成员。 - Killzone Kid

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