C++ Logo

std-proposals

Advanced search

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

From: Sébastien Bini <sebastien.bini_at_[hidden]>
Date: Mon, 26 Sep 2022 16:20:33 +0200
On Mon, Sep 26, 2022 at 2:03 PM Edward Catmur <ecatmur_at_[hidden]>
wrote:

> On Mon, 26 Sept 2022 at 12:28, Sébastien Bini <sebastien.bini_at_[hidden]>
> wrote:
>
>> On Mon, Sep 26, 2022 at 12:31 PM Edward Catmur <ecatmur_at_[hidden]>
>> wrote:
>>
>>> On Mon, 26 Sept 2022 at 10:36, Sébastien Bini <sebastien.bini_at_[hidden]>
>>> wrote:
>>>
>>>> My bad, I meant no user-provided *relocation, move or copy*
>>>> constructors.
>>>>
>>>
>>> Sure. I'd be a bit concerned that this is too restrictive, though it
>>> shouldn't be a problem for tuple types as far as I can tell, but they might
>>> want to user-provide special member functions for logging? This should be
>>> called out, though.
>>>
>>
>> I wouldn't know otherwise how to have the guarantee that all subobjects
>> are independent from one-another. I also wonder whether we should impose no
>> user-provided default constructor.
>>
>
> I think it's one or the other; if access is checked then the code
> accessing the private/protected base/member is taking responsibility for
> maintaining any relevant invariants. So long as access checking is
> bulletproof I don't think we need to worry too much about a class breaking
> its own invariants; the union hack would always be a possibility. I'd
> remove that requirement (no special member functions) and say that if you
> break invariants by decomposing into private bases and members that's your
> own responsibility.
>

I agree with the class invariant part.
But the no user-provided reloc, move and copy ctor clause was not motivated
because of the invariant, but because we wanted to be sure that subobjects
of a class could be independently relocated or destroyed. So at minimum we
must have a default destructor. And that's not enough.

struct PainterGuard
{
    Painter* _p;
    StateGuard(Painter& p) : _p{&p} { _p->save(); }
    ~StateGuard() { _p->restore(); }
};

class PainterWithGuard
{
public:
    PainterWithGuard(Painter p) : _p{reloc p}, _guard{_p} {}
    PainterWithGuard() : _guard{_p} {}
    PainterWithGuard(PainterWithGuard) /* reloc ctor */ { _guard._p = &_p; }
public:
    Painter _p;
private:
    PainterGuard _guard;
};

This is a perfectly valid class. Its default destructor works fine.
Unfortunately, from outside the class (with no access to private
data-members), doing:
`auto [p] = std::decompose<&PainterWithGuard::_p>(reloc painterWithGuard);`
Will call `restore()` on a destructed/relocated object.

Likewise doing (in the class implementation this time):
`auto [p, g] = std::decompose<&PainterWithGuard::_p,
&PainterWithGuard::_guard>(reloc painterWithGuard);`
Will construct a PainterGuard that points to invalid data. This one can be
incriminated to the class writer as it needs access to private data.

I agree this class has a bad design, but its safe to use otherwise.
std::decompose shouldn't cause crashes on badly designed classes.

IMO the no user-provided reloc, move and copy ctor clause gives us that
extra guarantee, that there are no relationships between subobjects and as
such it is safe to independently relocate or destroy them.

Received on 2022-09-26 14:20:45