放置新的断点和引用

Placement new breaks consts and references?

本文关键字:引用 断点      更新时间:2023-10-16

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

下面的代码是允许的

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和/或引用而不是其他原因而无效:它之前是对Foo的引用,之后是对Foo的引用。

谁能解释一下这种情况背后的原因?

编辑

谢谢你的回答。我不相信"保证它不会改变"的说法,因为我们目前不允许优化器缓存引用,例如:

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不是——序列点使缓存值无效:为什么要删除/放置新豁免?

我相信这样做的动机是允许编译器缓存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。析构操作违反了引用成员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对象永远不会改变它们的值)将被破坏。

据我所知,这只是语义正确性的问题,以及优化器可能做出的相应假设。想想看:

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。正如标准所说,最后两行中的销毁加新构造"自动更新所有引用以引用(逻辑上)新对象",因此现在z内部的const-reference发生了变化,尽管承诺是常量。

为了避免这种对const-正确性的暗箭式违反,禁止整个就地重建。

优化。假设我有:

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对象后,有权假设值不会改变;优化器可能只是保留该值在寄存器中跨放置new,并再次输出。

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

如果某些内容是const限定的,则不应该修改它。延长其使用寿命是一种会带来严重后果的修改。(例如,考虑析构函数是否有副作用。)