我知道在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()
不会改变自己a
,incval
也不会改变b
。请注意,在main
中,您甚至可以声明const A a(b);
而不是A a(b);
并且所有编译都相同。
这工作了几十年,在你的样本中,你只是在做同样的事情:你只是用λ替换了B类。
编辑
更改了 main() 以反映注释。
问题是逻辑常量与按位常量的问题之一。 编译器对程序的逻辑含义一无所知,并且仅强制按位常量。 由你来实现逻辑常量。这意味着在像你展示的情况中,如果指向内存从逻辑上讲,对象的一部分,您应该避免在const 函数,即使编译器会允许你(因为它不是一部分对象的按位图像)。 这也可能意味着,如果部分对象的按位图像不是对象(例如,嵌入的引用计数或缓存值),您制作 mutable
,甚至抛弃康斯特,如果你修改它没有修改对象的逻辑值。
const 功能仅有助于防止意外误用。 它不是为了防止专用软件黑客攻击而设计的。 它与私有和受保护的成员资格相同,有人总是可以获取对象的地址并沿着内存递增以访问类内部,没有办法阻止它。
所以,是的,你可以绕过康斯特。 如果没有别的,你可以简单地在内存级别更改对象,但这并不意味着 const 被破坏了。