C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Relocation in C++

From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
Date: Tue, 1 Feb 2022 13:51:07 +0100
wt., 1 lut 2022 o 12:00 Sébastien Bini via Std-Proposals
<std-proposals_at_[hidden]> napisał(a):
>
> Hello Gašper,
>
> Thank you for the first reply!
>
> I had a read of some of these proposals, but I was not entirely satisfied. It's true my paper is lacking a comparison section, I'll add it when time permits :)
>
> About the maybe_destroy:
> This is an annoying pattern, I admit. However, I believe it's tackled properly in the proposal:
> - Relocation should only happen with the "reloc" keyword. This is the only safe way to do relocation (at least in the proposal :p).
> - The reloc keyword can only relocate plain values. If a reference is passed to reloc, it yields a compilation error, especially for this reason.
> - One of the goals of the reloc keyword is to prevent the call to the destructor.
> - People are heavily discouraged from calling the relocation constructor directly, especially because it does not prevent object destruction by regular means. Exactly like how people are discouraged from calling the destructor of an object explicitly, unless they know what they are doing. This determent is emphasized by the use of const_cast that is required to cast a regular reference to a relocation reference. const_cast was chosen because of the sense of caution it conveys.
> - The relocation constructor can still be called directly (given it has public visibility) to handle complex memory management cases for programmers that know what they are doing (for instance, std::vector implementation?). This is similar to destructors today: although they appear in class definition, they must not be called explicitly unless you know what you are doing.
>
> The use of reloc makes your code snippet safe:
> bool maybe_destroy(T x) {
> if (rand() % 2) { reloc x; return true; }
> return false;
> }
> We had to remove the reference otherwise the reloc call will yield a compilation error.
>

This has one big flaw, `T x` destructor is called by the caller
function of `maybe_destroy` as the lifetime of `T x` needs to be
longer than the call to this function.
To be able to skip this destructor there should be ABI break to
transfer info about destruction of variables in function.

https://godbolt.org/z/P4s3j7soG
```
#include <iostream>

struct S
{
    const char* s;
    S(const char* t){ s = t; std::cout << "S(" << s << ")\n"; }
    ~S() { std::cout << "~S(" << s << ")\n"; }
};

S foo(S s, S b)
{
    return S("R");
}

int main()
{
    auto r = (
        S("Exp"),
        foo(
            (S("Dummy"), S("In1")),
            S("In2")
        )
    );
}
```
Will result in:
```
S(Exp)
S(In2)
S(Dummy)
S(In1)
S(R)
~S(In1)
~S(Dummy)
~S(In2)
~S(Exp)
~S(R)
```
`~S(In2)` si cleary clean up by `main` not `foo`, now how will work
where `foo` destroy `S b`?


> If users still want to stick with calling the relocation construction explicitly (which is not how relocation is done in this proposal, again relocation should only happen with the reloc operator), then
> bool maybe_destroy(T& x) {
> if (rand() % 2) { T{const_cast<T~>(x)}; return true; }
> return false;
> }
> will indeed lead to the object being destructed twice (i.e. the second time when it reaches its end of scope at the call site). However, given C++ philosophy "protect against Murphy, not Machiavelli", this is a code written knowing it would lead to disaster, which is very similar to:
> bool maybe_destroy(T& x) {
> if (rand() % 2) { x.~T(); return true; }
> return false;
> }
> which is permitted today (both snippets conditionally call a destructor on the object).
>
> I believe this answers your question, as the proposal provides a safe way to do relocation via the reloc keyword. The maybe_destroy pattern will not be encouraged by the proposal. The proposal introduces no vulnerability that is not already there in the language. Programmers can still screw things up but that is always the case in C++.
>
> Sébastien Bini
>
> On Tue, Feb 1, 2022 at 11:04 AM Gašper Ažman <gasper.azman_at_[hidden]> wrote:
>>
>> Hi Sebastien,
>>
>> you sure made a pretty long write-up! What I'm missing on the first skim-through is a thorough review of the currently published papers in the space and answers to the previously surfaced objections.
>>
>> Some of the papers in this space:
>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1144r5.html
>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4158.pdf
>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1029r3.pdf
>> http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0023r0.pdf
>>
>> I also couldn't find any way that your proposal handles maybe-destroy references. For instance, how do you handle
>>
>> bool maybe_destroy(T& x) {
>> if (rand() % 2) { T(static_cast<T~>(x)); return true; }
>> return false;
>> }
>>
>> The compiler will still emit the destructor call for x in the caller of maybe_destroy - there's nothing in the interface that communicates whether an object has been destroyed or not, *to the compiler*. Being able to handle this case has been the major stumbling block for pretty much every proposal in this space.
>>
>> G
>>

Received on 2022-02-01 12:51:19