【问题标题】:Does this code subvert the C++ type system?这段代码是否颠覆了 C++ 类型系统?
【发布时间】:2012-06-20 02:39:54
【问题描述】:

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

但是,这段代码显然通过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(而不仅仅是指针) -- 似乎暗示 (by Stroustrup) 在构造之后以任何方式修改 bc 会导致未定义的行为,即使构造函数和调用者在没有看到彼此定义的情况下无法知道这一点。

【问题讨论】:

  • 这有点跑题了,但是void (void) 是一个不推荐使用的构造,因为void () 自 C++98 和 C99 以来已经做了同样的事情。
  • @moshbear:我不知道是什么让我写的(向后兼容 C 或其他东西?!);修好了,谢谢。 :-)
  • 我喜欢你的O:) 笑脸有双重含义,即快乐的天使和震惊的惊恐的脸,这取决于你从哪个方向阅读。
  • 您可以在这里找到更多输入:stackoverflow.com/q/9939399/15161(人们不喜欢的类似问题:))
  • 这是我受stackoverflow.com/questions/11091385/…启发的问题,它也不需要另一个结构;)

标签: c++ constants const-correctness aliasing


【解决方案1】:

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

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

如果您将其声明为

const BreakConst bc = ...

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

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

【讨论】:

  • 如果我不是在main 中创建匿名方法,而是在构造函数中说BreakConst() : fn([&amp;]() { counter++; }), counter(0) { },那么同样的推理是否适用?在那种情况下,该方法看起来像是“在对象的上下文中”,但我不知道......
  • Heinzi:我很想看看标准中支持你推理的一些文本。
  • @Heinzi:我想我的问题是,如果我将 lambda 迁移到构造函数,我随后可以说const BreakConst bc;,因为bc 本身 现在是 const (而不仅仅是指针)——似乎意味着在构造之后以任何方式修改 bc 应该会导致 UB,即使构造函数和调用者没有办法知道这一点看到彼此的定义。这是正确的吗?
  • @Heinzi 因为似乎没有其他人反驳这一点:C++ 非常肯定确实有 const 对象,并且试图修改一个是未定义的行为(mutable 对象的成员除外)。跨度>
【解决方案2】:

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

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

来自 [7.1.6.1] cv 限定符

指向 cv 限定类型的指针或引用不需要实际指向 或引用一个 cv 限定的对象,但它被当作是这样处理;一种 const 限定的访问路径不能用于修改对象,即使 引用的对象是非常量对象,可以通过以下方式修改 其他一些访问路径。

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

【讨论】:

  • 一个常量对象?那是从哪里来的?我以为只是指针/引用是 const。
  • @Mehrdad: const Foo f; 声明一个 const 对象
  • 正如我对标准的引用所说 - const 引用 被视为指向 const 对象
  • @RafałRawicki:嗯,这很有趣……所以你是说这是 UB?我想我的问题是,为什么它是 UB? (即在代码的哪一部分,准确地说,我违反了什么?)
  • 我回答的是关于颠覆类型系统本身的问题。在第二个想法您的代码示例不使用 const 引用,所以它是正确的。这是一个很好的例子,说明为什么正式证明函数(或者我们应该说是函数调用)是纯的非常困难。
【解决方案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() 以反映评论。

【讨论】:

  • 您的示例没有解决 const object 的问题(在我更新问题后,基于另一个答案)。 这就是让我害怕的地方。
  • @Mehrdad:查看我的编辑。这是你的意思吗?有用! (或者......它没有,因为根据你的概念,它不应该)
【解决方案4】:

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

    • const 特性仅仅有助于防止意外误用——我想问题是,为什么这不属于“意外误用”?
    • 在我的下一句“它不是为防止专用软件黑客攻击而设计的”下更多 ;-) 没有办法在内存级别防止数据攻击。
    • “在内存级别”是什么意思?还有哪些其他级别?
    • 例如,如果你声明一个类并命名一个私有成员,我不能用另一个类的代码修改它(除非你采取措施允许它,例如让我成为朋友)。它在代码级别是安全的。但是,我可以获取您的对象的地址并在内存级别访问您的成员并更改它。没有办法阻止这种情况。这并不意味着“private”被破坏,只是它旨在防止随意滥用,而不是专用攻击。
    • 嗯...这是一个很好的观点,但这是一个略有不同的观点。据我所知,访问private 的东西不会 导致未定义的行为——但const 会!所以在我看来,如果有什么东西导致了 UB,你应该能够以某种方式防范它,但我不知道你怎么能在这里做到这一点。
    猜你喜欢
    • 1970-01-01
    • 2011-05-18
    • 1970-01-01
    • 2019-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-16
    • 1970-01-01
    相关资源
    最近更新 更多