此代码是否颠覆了C++类型系统

Does this code subvert the C++ type system?

本文关键字:C++ 类型系统 颠覆 代码 是否      更新时间:2023-10-16

我知道在C++中使用const方法意味着对象通过该方法是只读的,但它仍然可能更改。

但是,此代码显然通过const引用(即通过const方法)更改对象。

这个代码在C++合法吗?

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

如果没有:为什么不呢?

注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(而不仅仅是指针) - 似乎暗示(通过Stroustrup)在构造后以任何方式修改bc应该导致未定义的行为,即使构造函数和调用者如果不看到彼此的定义就无法知道这一点。

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

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

如果您将其声明为

const BreakConst bc = ...

然后 main 函数也会将其作为 const 对象处理,并且无法更改它。

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

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

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

从 [7.1.6.1] 简历限定符

指向 cv 限定类型的指针或引用实际上不需要指向或引用符合 cv 条件的对象,但它被视为符合条件的对象;一个常量限定的访问路径不能用于修改对象,即使引用的对象是非 const 对象,可以通过其他一些访问路径。

除了任何声明为可变 (7.1.1) 的类成员都可以修改,任何在 const 对象的生存期内修改其生存期 (3.8) 结果的尝试在未定义的行为中。

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

还要考虑这一点:

#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,但执行相同的操作:关键是常量不是传递的

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

这工作了几十年,在你的样本中,你只是在做同样的事情:你只是用λ替换了B类。

编辑

更改了 main() 以反映注释。

问题是逻辑常量与按位常量的问题之一。 编译器对程序的逻辑含义一无所知,并且仅强制按位常量。 由你来实现逻辑常量。这意味着在像你展示的情况中,如果指向内存从逻辑上讲,对象的一部分,您应该避免在const 函数,即使编译器会允许你(因为它不是一部分对象的按位图像)。 这也可能意味着,如果部分对象的按位图像不是对象(例如,嵌入的引用计数或缓存值),您制作 mutable,甚至抛弃康斯特,如果你修改它没有修改对象的逻辑值。

const 功能仅有助于防止意外误用。 它不是为了防止专用软件黑客攻击而设计的。 它与私有和受保护的成员资格相同,有人总是可以获取对象的地址并沿着内存递增以访问类内部,没有办法阻止它。

所以,是的,你可以绕过康斯特。 如果没有别的,你可以简单地在内存级别更改对象,但这并不意味着 const 被破坏了。