Placement new breaks consts and references?
Following the discussion on my answer to this question, apparently:
the following code is allowed
struct Foo {
int x;
};
Foo f;
Foo & f_ref = f;
(&f) -> ~Foo ();
new (&f) Foo ();
int x = f_ref .x;
but the following code is not allowed
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;
Because of $3.8/7
If, after the开发者_StackOverflow lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
- the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type ...
I can understand how a reference to f.x
could be invalidated when f ceases to exist, but I don't see why f_ref
should be invalidated purely because one of its members is const and/or reference and not otherwise: it was a reference to a Foo
before and is a reference to a Foo
afterwards.
Can someone please explain the rationale behind this condition?
Edit
Thanks for the answers. I don't buy the "guarantee it doesn't change" argument because we don't currently allow optimisers to cache referands, for example:
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
I don't see how do_it
is allowed to invalidate referenced values but operator new
isn't -- Sequence points invalidate cached values: why should delete/placement-new be exempt?
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.)
精彩评论