const对象与const对象和成员指针,构造函数漏洞

时间:2014-01-13 01:25:50

标签: c++ class pointers const-correctness

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

也被充分考虑过了。

3 个答案:

答案 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也不是构造函数的常量对象。这可能是以不同的方式设计的,但是常量对象的构造函数将比非常量对象的构造函数灵活得多;例如,他们总是必须初始化初始化列表中的所有成员;他们不能在构造函数体中使用循环等来设置复杂成员的值。