引用类型的数据成员提供有关恒常正确性"loophole"



我最近偶然发现了以下关于常量正确性的"漏洞":

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对象的任何修改都是未定义的行为,代码片段确实这样做了。

该程序不是格式不正确的(这将需要编译错误),因为在初始化inner时,cv限定符尚未生效。

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

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

这不是代码中的错误,请记住 const 指的是声明符的最内层元素。基本上,ConstMethod 上的常量做了引用:

Inner& const inner;

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

Inner * const inner;

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

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

这类似于

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

其中constXPtr的存在不会(也不应该)阻止人们通过xPtr修改x

值得注意的是,这种混叠的可能性阻止了许多潜在的编译器优化。

最新更新