C++ Logo

std-proposals

Advanced search

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

From: Avi Kivity <avi_at_[hidden]>
Date: Thu, 19 May 2022 14:41:52 +0300
On 18/05/2022 12.56, S├ębastien Bini wrote:
> Hi all,
>
> > The wrinkle here is that for a relocator synthesized from
> move+destroy, the
> > destination and source lifetime do overlap, observably
>
> I am honestly confused here: what kind of synthesized relocation are
> you talking about? The reloc operator does not perform move+destruct.
> If the object is not trivially relocatable, and does not have an
> accessible operator reloc() member function, then all that reloc does
> is constructing a new object using the move (or copy) constructor, no
> destruction is involved by reloc per-se:


The recoc'ed-from object has its lifetime ended immediately, so it is
destroyed. Hence a synthesized reloc operator is equivalent to move+destroy.


>
> // T is not trivially relocatable, and does not have an accessible
> operator reloc() member function
>
> void foo(T foo_obj);
>
> void bar()
> {
> T bar_obj;
> foo(reloc bar_obj); // equivalent to:
> foo(T{const_cast<T&&>(bar_obj)});
> // The destructor of bar_obj is not called at the end of the foo()
> function call.
> some_dummy_code();
> // The destructor of bar_obj is called here, as if std::move were
> used when calling foo
> }
>
> > 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.
>
> I am not sure how the move constructor gets involved in operator
> reloc() member function. What is proposed is:
> - to use trivial relocation
> - use the operator reloc()
> - or only move (knowing that the destructor will be called at some
> point later)


Since relocation says that the object's lifetime is ended immediately,
"some point later" is now.


>
> The operator reloc() member function is not designed to fallback to
> the move constructor + destructor (at least not in what is proposed
> currently).
>
> A class that provides any user-defined copy constructor, move
> constructor, or destructor will have its operator reloc() implicitly
> deleted. Class authors are still free to default it or provide a
> definition.
>
> If a class has any of its base class or data-member with an implicitly
> deleted operator reloc(), then its operator reloc() will be implicitly
> deleted as well. And writing a custom operator reloc() may prove to be
> complicated. Consider:
>
> // B has an implicitly deleted operator reloc(), as it provides for
> instance a user-defined move constructor and destructor.
> struct T : B
> {
> D data;
>
> // User defined operator reloc()?
> operator reloc(T&& src) :
> B{std::move(src)}
> /* data is initialized using default rules */
> { src.~B(); /* manually destruct B part */ }
> }
>
> My take is that if T's operator reloc() were defaulted, then it would
> get deleted instead of having a default definition, as B's operator
> reloc() is implicitly deleted.
>
> This is the same mechanism that applies to move constructors. AFAIK
> default-generated move-constructors do not delegate to their base
> class copy constructor if their base class move constructor is
> implicitly deleted. I am not sure it is a good idea to have operator
> reloc() make a synthesized relocation, calling move+destruct if the
> base class or data-member has an implicitly deleted operator reloc()
> member function, if that is what you are suggesting.
>
> > Actually, can we rethink this? One of the success criteria here is
> that we
> > should be able to pass std::unique_ptr (or equivalent) in registers. So
> > that means that we're accepting at least some level of ABI breakage.
> >
> > Any class that is trivially relocatable or has an accessible relocate
> > operator must either have been trivial already (in which case there
> is no
> > ABI impact) or have been explicitly opted in to relocation, by having a
> > relocator declared or defaulted. Since this is under the control of the
> > library author and/or user, I don't see it as an issue to say that
> > functions with parameters having trivial or user-defined relocate have a
> > different ABI (callee-destroy, or if necessary having automatic
> > accompanying flags in registers or on the stack). We accept that
> adding a
> > user-defined destructor, or virtual functions/bases, or changing data
> > members changes ABI, so why shouldn't defaulting/declaring a relocator?
>
> That's interesting, but I see some obstacles:
>
> operator reloc() is not necessarily opt-in. It may be implicitly
> declared and defined. This requires that the class has no user-defined
> constructors and destructors (among other things). A class may get an
> operator reloc() for free, without any changes in the class
> declaration nor in any of its base classes and data-members
> (recursively). Functions that will take such classes as a prvalue
> parameter will then have an ABI break?
> Under those circumstances the ABI break may not be necessary. Indeed
> such classes are POD types, and their destructor are a no-op. I guess
> we don't really care that their destructor call cannot be alleviated.
>
> For the other cases, operator reloc() is opt-in. However it is vital
> that most standard library classes support this operator reloc(). Most
> STL classes provide a user-defined constructor or destructor, and as
> such will get an implicitly deleted operator reloc(). If they don't
> default or provide their own operator reloc(), then any class that has
> an STL data-member will get its operator reloc() implicitly deleted as
> well. They will then struggle writing their own operator reloc() to
> support relocation, and will in addition be ruled out of trivial
> relocation (double pain).
>
> If the STL then explicitly supports operator reloc() (like it should
> be required) then any function having an STL parameter passed by value
> (like void foo(std::unique_ptr<T>); ) will get an ABI break. I really
> have no idea if that is acceptable.
>
> > 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.
>
> Given that, AFAIK, there is no synthesized relocation involved, then
> yes the relocator (operator reloc() member function) can be
> noexcept(true) by default. Then any exception that leaks through a
> relocator causes std::terminate to be called.
>
> S├ębastien

Received on 2022-05-19 11:41:59