Placement new breaks consts and references?

做~自己de王妃 提交于 2019-11-29 13:30:45

I believe the motivation is to permit the compiler to cache the values of const objects (note that's const objects, not merely referands of pointers-to-const and reference-to-const), and the addresses of referands of references, across calls to unknown code.

In your second example, the compiler can "see" firstly that the object has been created and destroyed, and secondly that it was re-created using the same value. But the authors of the standard wanted compilers to be allowed to turn this code:

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;

Into this:

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

because the reference member of f cannot be reseated, and hence must still refer to i. The destruct-and-construct operation violates the non-reaseatable-ness of the reference member x.

This optimization should not be particularly controversial: consider the following example, using a const object rather than an object with a const or reference member:

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

Here i is a compile-time constant, some_function_in_another_TU cannot validly destroy it and create another int in its place with a different value. So the compiler should be allowed to emit code for std::cout << 1; The idea is that the same should be true by analogy for const objects of other types, and for references.

If a call to unknown code could reseat a reference member, or alter the value of a const data member, then a useful invariant of the language (references are never reseated and const objects never change their values) would be broken.

As far as I can tell, it's just a matter of semantic correctness, and the adherent assumptions that the optimizer may make. Consider this:

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?

The object z may reasonably expect its Foo member reference to be constant and thus refer to important. As the standard says, the destruction plus new construction in the last two lines "automatically updates all references to refer to the (logically) new object", so now the const-reference inside z has changed, despite the promise of being constant.

To avoid this backstabbing violation of const-correctness, the entire reconstruction-in-place is forbidden.

Optimization. Suppose I have:

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;
}

The compiler, having seen a const int object, has the right to suppose that the value doesn't change; the optimizer might simply keep the value in a register accross the placement new, and output it again.

Note that your example is actually quite different. The data member has type int const&; it is a reference (and references are always const), so the compiler can assume that the reference always refers to the same object. (The value of this object may change, unless the object itself is also const.) The compiler can make no such assumption about the value of the object it refers to, however, since i (in your case) can clearly change. It is the fact that the reference itself (like all references) is immutable that causes the undefined behavior here, not the const that you've written.

If something is const-qualified, you're not supposed to modify it. Extending its lifetime is a modification with serious consequences. (For example, consider if the destructor has side-effects.)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!