20

我知道const在 C++ 中有一个方法意味着一个对象通过该方法是只读的,但它可能仍然会改变。

然而,这段代码显然是通过const引用(即通过const方法)改变了一个对象。

这段代码在 C++ 中合法吗?

如果是这样:它是否破坏const了类型系统的-ness?为什么/为什么不?

如果不是:为什么不呢?

注意 1:我已经对示例进行了一些编辑,因此答案可能是指较旧的示例。

编辑 2:显然你甚至不需要 C++11,所以我删除了那个依赖。

#include <iostream>

using namespace std;

struct DoBadThings { int *p; void oops() const { ++*p; } };

struct BreakConst
{
    int n;
    DoBadThings bad;
    BreakConst() { n = 0; bad.p = &n; } 
    void oops() const { bad.oops(); }  // can't change itself... or can it?
};

int main()
{
    const BreakConst bc;
    cout << bc.n << endl;   // 0
    bc.oops();              // O:)
    cout << bc.n << endl;   // 1

    return 0;
}

更新:

我已将 lambda 迁移到构造函数的初始化列表,因为这样做可以让我随后说const BreakConst bc;,这 - 因为bc 它本身现在是 const (而不仅仅是指针) - 似乎暗示(由 Stroustrupbc以任何方式修改构造后应该导致未定义的行为,即使构造函数和调用者在没有看到彼此的定义的情况下无法知道这一点。

4

5 回答 5

12

oops() 方法不允许更改对象的常量。此外,它不这样做。它是你的匿名函数。这个匿名函数不在对象的上下文中,而是在允许修改对象的 main() 方法的上下文中。

您的匿名函数不会更改 oops() 的 this 指针(定义为 const ,因此无法更改),也不会从 this 指针派生一些非常量变量。它本身没有任何this-pointer。它只是忽略 this 指针并更改主上下文的 bc 变量(它作为参数传递给您的闭包)。此变量不是 const,因此可以更改。您还可以传递任何匿名函数来更改完全不相关的对象。这个函数不知道,它改变了存储它的对象。

如果您将其声明为

const BreakConst bc = ...

那么主函数也将其作为 const 对象处理并且无法更改它。

编辑: 换句话说: const 属性绑定到访问对象的具体左值(引用)。它不绑定到对象本身。

于 2012-06-18T06:20:34.007 回答
3

您的代码是正确的,因为您不使用 const 引用来修改 object。lambda 函数使用完全不同的引用,它恰好指向同一个对象。

一般来说,这种情况不会颠覆类型系统,因为 C++ 中的类型系统并没有正式保证不能修改 const 对象或 const 引用。然而 const 对象的修改是未定义的行为。

来自 [7.1.6.1] 的 cv-qualifiers

指向 cv 限定类型的指针或引用不需要实际指向或引用 cv 限定对象,但它被视为就好像它确实指向或引用了一个 cv 限定对象;const 限定的访问路径不能用于修改对象,即使引用的对象是非 const 对象并且可以通过其他访问路径进行修改。

除了可以修改任何声明为 mutable 的类成员(7.1.1)外,任何在 const 对象的生命周期(3.8)期间修改它的尝试都会导致未定义的行为。

于 2012-06-18T06:20:49.713 回答
3

我已经看到了类似的东西。基本上,您调用了一个成本函数,该函数调用了在不知情的情况下修改对象的其他内容。

也考虑一下:

#include <iostream>
using namespace std;

class B;

class A
{
    friend class B;
    B* pb;
    int val;
public:
    A(B& b); 
    void callinc() const;
    friend ostream& operator<<(ostream& s, const A& a)
    { return s << "A value is " << a.val; }
};

class B
{
    friend class A;
    A* pa;
public:
    void incval() const { ++pa->val; }
};

inline A::A(B& b) :pb(&b), val() { pb->pa = this; }
inline void A::callinc() const { pb->incval(); }


int main()
{
    B b;
    const A a(b);  // EDIT: WAS `A a(b)`
    cout << a << endl;
    a.callinc();
    cout << a << endl;
}

这不是 C++11,但也是一样的:关键是const 不是传递性的。

callinc()不会改变自己aincval也不会改变b。请注意,main您甚至可以声明const A a(b);而不是A a(b);所有内容都编译相同。

这从几十年开始就有效,在您的示例中,您只是在做同样的事情:只需将 B 类替换为 lambda。

编辑

更改了 main() 以反映评论。

于 2012-06-18T06:31:17.180 回答
2

问题是逻辑 const 与按位 const 之一。编译器对程序的逻辑含义一无所知,只强制执行按位 const。由你来实现逻辑常量。这意味着在你展示的情况下,如果指向的内存在逻辑上是对象的一部分,你应该避免在 const 函数中修改它,即使编译器允许你(因为它不是按位图像的一部分对象)。这也可能意味着,如果对象的按位图像的一部分不是对象的逻辑值的一部分(例如,嵌入式引用计数或缓存值),则您可以创建它 mutable,甚至丢弃 const,在以下情况下您修改它而不修改对象的逻辑值。

于 2012-06-18T08:10:48.483 回答
0

The const feature merely helps against accidental misuse. It is not designed to prevent dedicated software hacking. It is the same as private and protected membership, someone could always take the address of the object and increment along the memory to access class internals, there is no way to stop it.

So, yes you can get around const. If nothing else you can simply change the object at the memory level but this does not mean const is broken.

于 2012-06-18T08:56:26.427 回答