class Test
{
public:
Test() : i(0), ptr(&i) {}
int i;
int *ptr;
void change_const (int x) const { *ptr=x; }
};
int main()
{
const Test obj;
obj.ptr = &obj.i; // error
obj.change_const(99);
return 0;
}
虽然obj
ptr
的类型为int *const
,但构造函数可以指向i
类型的const int
。显然尝试这样做当然失败了。为什么构造函数提供有关const正确性的漏洞?其他非直接明显的漏洞,如
int *ptr;
const int **c_ptr = &ptr; // error
const int c = 10;
*c_ptr = &c;
*ptr = 20; // because here change of c possible
也被充分考虑过了。
答案 0 :(得分:4)
const
是一种语言级概念。也就是说,当您编译代码并将其作为机器代码执行时,所有数据都被视为数据或多或少。请注意,我说“或多或少”,因为我们忽略了这样一个事实:理论上,const
数据可以存储在只读页面中并在写入时触发页面错误;但由于页面大小的粒度,这种情况并不常见。所以发生的事情如下:
您的构造函数会将ptr
的值初始化为指向i
的地址。由于您的obj
对象为const
,因此您无法直接修改i
的值,而且您无法更改ptr
指向的位置。但是,您可以访问和操作ptr
指向的内存(在本例中为i
的值)。
因此,由于编译器不检查/知道/关心ptr
指向i
,因此它不会发现违反const
。相反,它只是看到您修改ptr
指向的数据。
答案 1 :(得分:2)
显然,构造函数(或者至少是初始化列表,如果不是ctor的主体)需要能够将值写入i
。
碰巧,C ++实现这一目的的方法是在构造函数(和析构函数)中使this
成为指向非const的指针。基本上,const
的{{1}} - ness在构造函数执行完之后才会开始。这就是漏洞存在的原因,因为对于如何构造obj
- 合格对象的技术问题有一个简单但不完美的解决方案。
也许原则上可以采用不同的方式。我想你需要一个单独的const
版本的构造函数,其中编译器应用不同的规则(就像普通的成员函数可以是const
),将数据成员视为const
,因此(1)允许初始化但未分配,(2)禁止const
的{{1}}初始化,因为后者的类型为ptr
。 C ++不会这样做,因此它有你所经历的这个漏洞。如果它确实这样做了,人们在某些情况下编写构造函数会有更多困难,所以这是一种设计权衡。
请注意,类似的&i
- 限定对象在其自己的构造函数或析构函数中不是int const*
。
答案 2 :(得分:1)
Steve Jessop回答了这个问题,但是对于它的价值,这里是标准的引用(强调我的):
12.1 / 4构造函数不应为虚拟(10.3)或静态(9.4)。可以为const,volatile或const volatile对象调用构造函数。构造函数不应声明为const,volatile或const volatile(9.3.2)。 const和volatile语义(7.1.6.1)不适用于正在构建的对象。它们在最派生对象(1.8)的构造函数结束时生效。不应使用ref-qualifier声明构造函数。
因此,即使创建了常量对象,*this
也不是构造函数的常量对象。这可能是以不同的方式设计的,但是常量对象的构造函数将比非常量对象的构造函数灵活得多;例如,他们总是必须初始化初始化列表中的所有成员;他们不能在构造函数体中使用循环等来设置复杂成员的值。