【问题标题】:Data member of reference type provides "loophole" around const-correctness引用类型的数据成员提供了围绕 const 正确性的“漏洞”
【发布时间】:2018-12-29 02:27:37
【问题描述】:

我最近偶然发现了以下关于 const 正确性的“漏洞”:

struct Inner {
  int field = 0;
  void Modify() {
    field++;
  }
};

struct Outer {
  Inner inner;
};

class MyClass {
public:
  Outer outer;
  Inner& inner; // refers to outer.inner, for convenience

  MyClass() : inner(outer.inner) {}

  void ConstMethod() const {
    inner.Modify();  // oops; compiles
  }
};

似乎还有可能使用此漏洞来修改声明为const 的对象,我认为这是未定义的行为:

int main() {
    const MyClass myclass;
    std::cout << myclass.outer.inner.field << "\n";  // prints 0
    myclass.ConstMethod();
    std::cout << myclass.outer.inner.field << "\n";  // prints 1
}

这让我很害怕,因为我似乎刚刚在一个不使用 const_cast 或使用 C 风格强制转换的程序中调用了与 const 正确性相关的未定义行为。

所以,我的问题是:

  • 我是否正确地说上述程序具有未定义的行为?
  • 如果是这样,这是一个语言错误吗?上述程序中是否有一行可以说不应该(可以合理地不编译)?
  • 在实践中是否应遵循一些准则以避免此类未定义的行为?

【问题讨论】:

  • 如果您打开警告,您对此有任何了解吗?我实际上并不认为这是未定义的,只是不可取的。
  • 它基于一个简单的事实,即在构造函数和析构函数内部,const 对象始终被视为非常量对象。是的,您可以使用它来解决 const 正确性规则而无需强制转换。
  • 不是答案,而是可以帮助防止这种情况发生的事情:en.cppreference.com/w/cpp/experimental/propagate_const
  • 正如我在下面提到的,相关问题是myclass.inner 是否“首次创建”为const。不是MyClass定义中的const,所以我真的不知道!

标签: c++ class reference constants


【解决方案1】:

这不应该是未定义的行为。是的,myclass.outerMyClass::ConstMethod() const 中被视为const,但它并没有开始作为const 存在,因此它应该可以通过非const 引用安全地修改。当然,这样做可能会让您的代码的读者和/或用户感到惊讶,因此您应该尽量避免这种情况。

这类似于

int x = 5;
int * xPtr = &x;
const int * constXPtr = &x;

其中constXPtr 的存在不会(也不应该)阻止人们修改xxPtr

值得注意的是,这种别名可能会阻止许多潜在的编译器优化。

【讨论】:

  • 糟糕。以上在某种意义上是有用的信息,但我只是注意到 OP 将myclass 声明为const。因此,这确实看起来像是类型系统中的一个不幸漏洞,创建了一个const 对象,同时绑定了一个非const 对它的引用。
  • myclass.outer 在“第一次创建”时被认为是const 仍然不完全清楚,这就是相关问题。
【解决方案2】:

Any modificationsconst 对象是未定义的行为,而 sn-p 确实做到了。

程序不是格式错误的(这将导致编译错误),因为在初始化 inner 时,cv-qualifiers 尚未生效。

从编译器的角度来看,发出警告需要它分析通向inner.Modify() 的所有代码路径,并证明inner 必须引用const 对象,这一般情况下是不可能的。

最好的建议可能是没有内部指针/引用,这无论如何都是邪恶的。

【讨论】:

  • 但是myClass.inner 不是 const——它只是在MyClass::ConstMethod() const中“被视为const”。
  • 该页面的关键引用(强调添加):“当对象首次创建时,使用的 cv 限定符...确定对象...。”
  • 糟糕,错过了 myclass 本身声明为 const。不过,不知道如何挽回我造成的伤害。对不起。
  • myclass.outer 在“第一次创建”时被认为是const,这仍然是相关问题。
  • @JoshuaGreen 子对象显然是对象的一部分,否则你将能够对类做任何你想做的事情。如果您仍然不相信,请阅读 thisthis
【解决方案3】:

这不是代码错误,请记住 const 指的是声明符的最里面的元素。基本上是 ConstMethod 上的 const 进行引用:

Inner& const inner;

当然这并没有什么真正的区别,因为引用不能被重新绑定。想想用指针做同样的事情,你会意识到内部仍然可以修改。如果是:

Inner * const inner;

你可以调用 inner->Modify()。

【讨论】:

  • 我知道它编译的为什么 - 这不是问题。问题是代码是否调用了未定义的行为,以及是否可以修改语言规则以在编译时捕捉到这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多