Date: Fri, 6 May 2022 13:50:22 +0100
On Fri, 6 May 2022 at 12:46, organicoman <organicoman_at_[hidden]> wrote:
> It's almost like a move operation, although with the move operation
> calling the destructor of the object moved-from is a defined behavior.
> But how to enforce 'not calling' the destructor of 'a' at the end of its
> scope?
> Similarly for 'c'?
>
Sorry, I made an error: I missed a * on construct_at, and also played a bit
fast-and-loose with the distinction between a (a reference) and its
referent (a dynamic object with storage buf). It will be clearer if a is a
pointer:
alignas(unique_ptr<int>) byte buf[sizeof(unique_ptr<int>)];
auto const a = construct_at(reinterpret_cast<unique_ptr<int>*>(buf), new
int); // #1
auto const b = relocate(a); // #2
The lifetime of the pointer a (dangling after #2) ends at the end of the
scope, but ending the lifetime of a (raw) pointer or reference does not
call a destructor (unless it is a lifetime-extending reference, which is
not the case here). The previous referent of a has dynamic lifetime, so the
language does not call its destructor automatically. Here the user is
required to know that the lifetime of the object constructed into buf by #1
was ended by #2.
auto const c = unique_ptr<int>(new int); // #3
auto const d = reloc c; // #4
The language knows that the lifetime of c was ended by #4 and so will not
call its destructor at the end of the scope. Further, the language enforces
that the function cannot odr-use c after #4; and static analysis may be
able to detect any dangling pointers or references.
A further example using a potential library feature:
auto e = std::optional(std::unique_ptr<int>(new int)); // #5
auto const f = e.pop(); // #6
This is similar in behavior to #1/#2, but std::optional tracks the lifetime
of the contained object via its engaged flag. #6 both relocates the object
*e into a prvalue materialized as f, and clears the engaged flag of e, so
that subsequent access to *e can be detected at runtime (in debug builds,
or via wide-contract e.value()) and also possibly by static analysis. Since
e is disengaged, when its lifetime ends it knows not to call the destructor
on a contained object.
> It's almost like a move operation, although with the move operation
> calling the destructor of the object moved-from is a defined behavior.
> But how to enforce 'not calling' the destructor of 'a' at the end of its
> scope?
> Similarly for 'c'?
>
Sorry, I made an error: I missed a * on construct_at, and also played a bit
fast-and-loose with the distinction between a (a reference) and its
referent (a dynamic object with storage buf). It will be clearer if a is a
pointer:
alignas(unique_ptr<int>) byte buf[sizeof(unique_ptr<int>)];
auto const a = construct_at(reinterpret_cast<unique_ptr<int>*>(buf), new
int); // #1
auto const b = relocate(a); // #2
The lifetime of the pointer a (dangling after #2) ends at the end of the
scope, but ending the lifetime of a (raw) pointer or reference does not
call a destructor (unless it is a lifetime-extending reference, which is
not the case here). The previous referent of a has dynamic lifetime, so the
language does not call its destructor automatically. Here the user is
required to know that the lifetime of the object constructed into buf by #1
was ended by #2.
auto const c = unique_ptr<int>(new int); // #3
auto const d = reloc c; // #4
The language knows that the lifetime of c was ended by #4 and so will not
call its destructor at the end of the scope. Further, the language enforces
that the function cannot odr-use c after #4; and static analysis may be
able to detect any dangling pointers or references.
A further example using a potential library feature:
auto e = std::optional(std::unique_ptr<int>(new int)); // #5
auto const f = e.pop(); // #6
This is similar in behavior to #1/#2, but std::optional tracks the lifetime
of the contained object via its engaged flag. #6 both relocates the object
*e into a prvalue materialized as f, and clears the engaged flag of e, so
that subsequent access to *e can be detected at runtime (in debug builds,
or via wide-contract e.value()) and also possibly by static analysis. Since
e is disengaged, when its lifetime ends it knows not to call the destructor
on a contained object.
Received on 2022-05-06 12:50:34