Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 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?

like image 839
spraff Avatar asked Sep 28 '11 17:09

spraff


People also ask

How much does it cost to get new brakes installed?

Brake service generally comes to approximately $200 to $500 per axle at a professional shop. Calipers are typically the most difficult and expensive aspect of the braking system to service. A single caliper can cost up to $130 and several will reach prices even higher.

Whats a good price for a brake job?

You can expect to pay between $30 and $75 per rotor and between $150 and $200 for the labor for each axle, for a total of between $250 and $500 per axle. Calipers are the most expensive part of the brake system to replace. A single caliper can cost around $130.

How long does it take a mechanic to replace brake pads?

They're usually going to take around one to three hours on average. There are a variety of brake repair types to consider, with some taking longer than others.

Does brake placement matter?

Does the placement of a car's brake calipers affect its braking performance? The caliper placement (top, bottom, front, rear) does not affect the stopping performance of the brakes, or the cooling of the pads and rotors.


2 Answers

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.

like image 122
Steve Jessop Avatar answered Sep 25 '22 02:09

Steve Jessop


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.

like image 32
Kerrek SB Avatar answered Sep 24 '22 02:09

Kerrek SB