Reusing data member storage via placement new during enclosing object's lifetime

后端 未结 2 1827
一生所求
一生所求 2021-01-03 10:46

This is a follow-up to my previous question where I seem to have made the problem more involved than I had originally intended. (See discussions in question and answer comme

相关标签:
2条回答
  • 2021-01-03 11:20

    If a is destroyed (whether by delete or by falling out of scope), then t.~T() is called, which is UB if t isn't actually a T (by not calling destruct).

    This doesn't apply if

    • the destructor of T is trivial, or
    • for delete U is derived from T, or
    • you're using a destroying delete

    After destruct is called you are not allowed to use t if T has const or reference members (until C++20).

    Apart from that there is no restriction on what you do with the class as written as far as I can see.

    0 讨论(0)
  • 2021-01-03 11:34

    This answer is based on the draft available at http://eel.is/c++draft/

    We can try to apply (by checking each condition) what I've decided to call the "undead object" clause to any previous object that used to exist, here we apply it to the member t of type T:

    Lifetime [basic.life]/8

    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:

    (8.1) the storage for the new object exactly overlays the storage location which the original object occupied, and

    (8.2) the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and

    (8.3) the original object is neither a complete object that is const-qualified nor a subobject of such an object, and

    (8.4) neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).

    Conditions 1 and 2 are automatically guaranteed by the use of placement new on the old member:

    struct A {
        T t /*initializer*/; (...)
    
        void destruct() { (...)
            ::new(static_cast<void*>(&t)) T /*initializer*/;
        }
    

    The location is the same and the type is the same. Both conditions are easily verified.

    Neither A objects created:

    auto a = new A;
    ...
    A b; /*alternative*/
    

    are const qualified complete objects so t isn't a member of a const qualified complete object. Condition 3 is met.

    Now the definition of potentially-overlapping is in Object model [intro.object]/7:

    A potentially-overlapping subobject is either:

    (7.1) a base class subobject, or

    (7.2) a non-static data member declared with the no_­unique_­address attribute.

    The t member is neither and condition 4 is met.

    All 4 conditions are met so the member name t can be used to name the new object.

    [Note that at no point I even mentioned the fact the subobject isn't a const member not its subobjects. That isn't part of the latest draft.

    It means that a const sub object can legally have its value changed, and a reference member can have its referent changed for an existing object. This is more than unsettling and probably not supported by many compilers. End note.]

    0 讨论(0)
提交回复
热议问题