安置新的制动器成本和参考?

时间:2011-09-28 17:00:01

标签: c++ reference const c++11 placement-new

关于my answerthis question的讨论,显然是:

以下代码 允许

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-quali fi ed或引用类型的非静态数据成员。
  •   

我可以理解当f不再存在时,对f.x的引用如何无效,但我不明白为什么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不是 - Sequence points invalidate cached values:为什么要删除/ placement-new是免税的?

4 个答案:

答案 0 :(得分:7)

我认为动机是允许编译器缓存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对象永远不会更改它们的值)被打破了。

答案 1 :(得分: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。正如标准所说,最后两行中的破坏加上新构造“自动更新所有引用以引用(逻辑上)新对象”,所以现在z内的const-reference已经改变,尽管有希望保持不变。

为避免这种违反常规正确性的行为,禁止整个就地重建。

答案 2 :(得分: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), 所以编译器可以假设引用总是引用相同的 宾语。 (除非对象本身,否则此对象的值可能会更改 也是const。)编译器 但是,因为i,所以不能对它引用的对象的值做出这样的假设。 (在你的情况下)可以明显改变。这是参考的事实 本身(像所有引用一样)是不可变的,导致这里的未定义行为,而不是const 你写的。

答案 3 :(得分:-1)

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