9

在讨论了这个问题的回答之后,显然:

允许以下代码

struct Foo {
    int x;
};

Foo f;
Foo & f_ref = f;

(&f) -> ~Foo ();
new (&f) Foo ();

int x = f_ref .x;

但不允许使用以下代码

struct Foo {
    const int & x;           // difference is const reference
    Foo (int & i) : x(i) {}
};

int i;
Foo f (i);
Foo & f_ref = f;

(&f) -> ~Foo ();
new (&f) Foo (i);

int x = f_ref .x;

因为$3.8/7

如果在对象的生命周期结束之后,在对象占用的存储空间被重用或释放之前,在原始对象占用的存储位置创建一个新对象,一个指向原始对象的指针,一个指向原始对象的引用引用原始对象,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,可用于操作新对象,如果:

  • 原始对象的类型不是 const 限定的,并且,如果是类类型,则不包含任何类型为 const 限定或引用类型的非静态数据成员...

我可以理解f.x当 f 不再存在时引用如何失效,但我不明白为什么f_ref应该仅仅因为它的成员之一是 const 和/或引用而不是其他原因而失效:它是对 aFoo之前和是对aFoo之后的引用。

有人可以解释这种情况背后的理由吗?

编辑

感谢您的回答。我不购买“保证它不会改变”的论点,因为我们目前不允许优化器缓存引用,例如:

struct Foo {
    const int & x;
    Foo (const int & i) : x(i) {}
    void do_it ();
};

int i;
Foo f (i);
const int & ii = f.x;

f .do_it (); // may modify i
std :: cout << ii; // May NOT use cached i

我看不到如何do_it允许使引用的值无效,但operator new不是—— 序列点使缓存的值无效:为什么要免除 delete/placement-new?

4

4 回答 4

8

我相信动机是允许编译器缓存const对象的值(请注意,这是 const对象,而不仅仅是指向 const 和引用 const 的引用),以及引用的引用的地址,跨越对未知的调用代码。

在您的第二个示例中,编译器首先可以“看到”该对象已被创建和销毁,其次它是使用相同的值重新创建的。但是该标准的作者希望允许编译器转换此代码:

struct Foo {
    const int & x;
    Foo (int & i) : x(i) {}
};

int i = 1;
Foo f(i);

some_function_in_another_TU(&f);

std::cout << f.x;

进入这个:

struct Foo {
    const int & x;
    Foo (int & i) : x(i) {}
};

int i = 1;
Foo f(i);

some_function_in_another_TU(&f);

std::cout << i;           // this line is optimized

因为 的引用成员f不能被重新定位,因此仍然必须引用i. destruct-and-construct 操作违反了引用成员的不可恢复性x

这种优化不应该特别有争议:考虑以下示例,使用const对象而不是具有const或引用成员的对象:

const int i = 1;
some_function_in_another_TU(&i);
std::cout << i;

i是一个编译时常量,some_function_in_another_TU不能有效地销毁它并int在其位置创建另一个具有不同值的常量。所以编译器应该被允许为std::cout << 1;其他类型的 const 对象和引用发出类似的代码。

如果对未知代码的调用可以重新定位引用成员或更改const数据成员的值,那么语言的有用不变量(引用永远不会重新定位并且 const 对象永远不会改变它们的值)将被破坏。

于 2011-09-28T17:09:42.307 回答
5

据我所知,这只是语义正确性的问题,以及优化器可能做出的假设。考虑一下:

Bar important, relevant;

Foo x(important);  // binds as const-reference

Zoo z(x);  // also binds as const reference

do_stuff(z);

x.~Foo();
::new (&x) Foo(relevant);  // Ouch?

对象z可以合理地期望其Foo成员引用是常量,因此引用important. 正如标准所说,最后两行中的破坏加上新构造“自动更新所有引用以引用(逻辑上)新对象”,所以现在内部的 const-referencez已经改变,尽管承诺保持不变。

为了避免这种对 const 正确性的背叛,整个就地重建是被禁止的。

于 2011-09-28T17:09:20.137 回答
2

优化。假设我有:

struct Foo
{
    int const x;
    Foo( int init ) : x( init ) {}
};

int
main()
{
    Foo a( 42 );
    std::cout << a.x << std::endl;
    new (&a) Foo( 3 );
    std::cout << a.x << std::endl;
    return 0;
}

编译器看到一个const int对象,有权假设该值没有改变;优化器可能只是将值保存在新位置的寄存器中,然后再次输出。

请注意,您的示例实际上完全不同。数据成员的类型为int const&; 它是一个引用(并且引用始终是 const),因此编译器可以假定该引用始终指向同一个对象。(这个对象的值可能会改变,除非对象本身也是常量。)但是,编译器不能对它所引用的对象的值做出这样的假设,因为i (在你的情况下)可以明显改变。事实上,引用本身(像所有引用一样)是不可变的,这会导致此处出现未定义的行为,而不是const 您编写的行为。

于 2011-09-28T17:27:33.367 回答
-2

如果某些东西是 const 限定的,那么你不应该修改它。延长其使用寿命是一种具有严重后果的修改。(例如,考虑析构函数是否有副作用。)

于 2011-09-28T17:10:30.307 回答