C++ Logo


Advanced search

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

From: Thiago Macieira <thiago_at_[hidden]>
Date: Tue, 17 May 2022 18:27:04 -0700
On Tuesday, 17 May 2022 17:31:08 PDT Edward Catmur wrote:
> > If x's relocator throws, then when that exception is thrown, both source
> > and
> > destination objects have no lifetime. The destination's has never started
> > and the source has irrevocably ended the moment the relocator started.
> > There's no going back.
> The wrinkle here is that for a relocator synthesized from move+destroy, the
> destination and source lifetime do overlap, observably. So one argument
> might be that in that case the relocator should have a (weak) exception
> guarantee that the source lifetime persists. However, that would be
> inconsistent with the behavior in the case of a relocator proper, so I
> agree that the synthesized behavior should be to destroy the source object
> if an exception is thrown during the move sub-operation.

I agree. The moment a destructor starts, the lifetime of the object will end.
You can unroll a creation, but you can't unroll a destruction. Objects whose
destructors threw are still destroyed.

The question is only how that is accomplished. I'd go ahead and say that, like
destructors, failing to properly destroy everything is the destructor's fault.
Therefore, a relocator -- synthesized or not -- should properly destroy the
source, whether it throws or not. If it fails to do so, then it's an
improperly-written relocator. As you said:

> Yes, you're correct. However note that if we add one more data member z, we
> can end up in this situation: x successfully relocates, y throws, but to
> release the resource T needs x and z.

This is improperly-coded and is the developer's fault.

> But actually (or at least potentially) throwing move constructors are
> reasonably common, so if we wrap them in an automatically generated
> noexcept-by-default synthesized special member function/operator, we're
> running the risk of destroying the credibility of the noexcept guarantee -
> we do not want to find that people (e.g. container authors) are avoiding
> calling relocate because they're worried about std::terminate from an
> automatically noexcept relocator and are instead performing move+destroy
> manually.

That's fine, but it is not a convincing argument to me.

> So I think that given that a relocator may (possibly invisibly) call a
> possibly-throwing move constructor, it cannot be noexcept by default.
> Instead, I would say that if a class has a user-defined destructor (and so
> is suspected of owning resources directly), and if at least one of its
> bases-and-members has throwing relocate, then its relocator cannot be
> defaulted (declared or defined as defaulted) but must be user-defined (or
> deleted). Also, a user-defined relocator (i.e. one not declared as
> defaulted or deleted) should not be noexcept if unspecified; either they
> should be noexcept(false) if unspecified, or a noexcept specification
> should be required.

You'll never get a "suspected of having" in the standard. The choice needs to
be by the signature of the methods involved. If those are synthesised, then
the signature is obtained from the members of that class and its base
class(es), but that does not change the rule.

We have a precedent for noexcept(true)-by-default and that's the destructor. I
think it could be acceptable to make the relocator obey the same rule because
after it's run -- and whether it has thrown or not -- one object's lifetime
has ended.

We also have the choice here whether such synthesised relocator is permissible
in the first place if either the move constructor or the destructor are
noexcept(false). That is, the rule would be that it's deleted by default and
if one wants to have a relocator in such a class, they'll have to explicitly
declare one. We can always relax this rule later.f

Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
   Software Architect - Intel DPG Cloud Engineering

Received on 2022-05-18 01:27:07