C++ Logo

std-proposals

Advanced search

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

From: Giuseppe D'Angelo <giuseppe.dangelo_at_[hidden]>
Date: Mon, 2 May 2022 13:30:19 +0200
Hello!

On 02/05/2022 12:23, S├ębastien Bini wrote:
> Hello Giuseppe,
>
> Thank you for your feedback.
>
> > I'm probably missing something, but from what you say above and from the
> > TempDir example on page 7, all of this sounds like existing move
> > semantics with the additional enforcement that a moved-from object isn't
> > ever touched again (not even reassigned / reset).
>
> You could view it like that. It also allows for (a) better optimization
> (relocation constructor can be better optimized in some cases, and
> trivial relocation (move+destruct optimized into a memcpy) can happen),

I think this is what I meant by "move semantics plus the p1144 bits",
that is, you need to tag types for which this can happen.


> (b) better semantics (it better conveys the intent), and (c) the move of
> const volatile objects.
>
> The point is also, that from a class design point of view, that the move
> constructor may break the class purpose as the moved-from instance must
> remain usable.

What do you mean precisely by "must remain usable"?

A moved-from instance must be able to:

1) safely be destroyed
2) safely be reassigned to
3) (possibly) safely be self-swapped

In 100% of the classes I've written, implementing move semantics in a
way that satisfies 1) will also make it satisfy 2) and 3) with no extra
work.

This is surely my biggest overall criticism: given that the destructor
is still called after a reloc operation, how is that different from the
existing move semantics?


>
> > (From a syntax point of view, I'm not sure how that is desirable, as one
> > could no longer something like `other = reloc obj; delete &obj;`, but I
> > don't think it's a particularly compelling use case...)
>
> Indeed, I don't see a case where you would need to reuse the relocated
> object. If you do need to, then move semantics are what you need.

There's still a question pending here, however. In

   auto ptr = &obj;
   other = reloc obj;
   delete ptr;

Does the reloc operation invalidate ptr? What's the rule in general for
pointers and references to a relocated object, do they become invalid?
Can I use `*this` in the object's destructor?


>
> > Anyways, a justification for relocation over move semantics on page 3
> > says that "the move constructor performs extra operations to ensure that
> > the moved-from object remains at a valid state." This is not universally
> > true. It is true for the Standard Library (and makes certain std::list
> > implementations of move operations expensive / noexcept(false)); that's
> > just stdlib's stance.
>
> Indeed it is not universally true. The phrasing may be too general, but
> there are indeed cases where the move constructor is:
> - either not trivial to implement, because of that moved-from instance
> that must remain valid

It does not need to remain valid. A moved-from instance can be merely be
partially-formed.


> - either performs extra costly operations that could be avoided, had we
> known that the moved-object will not be reused.

But do you have concrete examples where the benefits would be
significant? std::list is a case of the stdlib shooting itself in the foot.


>
> > A class author can always state that a moved-from
> > object of their class is only partially formed. In this last case, I am
> > not sure the paper convincly justifies operator reloc over the status
> > quo (which includes clang-tidy "use after move" checks and so on).
>
> Stating that the moved-from object is invalid and should not be touched
> again is (in my opinion) a bad class design. You may have a look at
> https://herbsutter.com/2020/02/17/move-simply/
> <https://herbsutter.com/2020/02/17/move-simply/>, which further
> motivated me to write this paper. Quoting Herb Sutter: "In C++, an
> object is valid (meets its invariants) for its entire lifetime, which is
> from the end of its construction to the start of its destruction (see
> [basic.life]/4). Moving from an object does not end its lifetime, only
> destruction does, so moving from an object does not make it invalid or
> not obey its invariants." Relocation allows to make an object invalid
> before it is destructed, albeit it is never touched again except by its
> destructor.

I (and many others) profoundly disagree with that blog post's
conclusion. Please review P2027, P2345 and the excellent talk by Marc
Mutz at MeetingC++. The bottom line is that moved-from objects do not
need to be valid, but only partially-formed.

Clearly you are free to disagree with the partially-moved stance, but
then there needs to be profound motivation in the paper regarding why
relocation is more beneficial than designing classes according to that
criterion.



> The classes you speak about would be the first one to benefit from the
> relocation constructor (which ensures the relocated-instance is not
> reused) and could also mark their move constructor as deleted. This
> would respect the language philosophy.

Sorry, what classes are you referring to?

The paper and I were referring to std::list as example of class that
needs to do more than needed in its move constructor (specifically: in
some implementations it has to reallocate a sentinel node, making its
move operations noexcept(false)). But the reasons for this are twofold;
one is stdlib's position of "valid-but-unspecified" state, which
requires such a reset, and the other one is that vendors didn't want to
pay for an ABI break (because you _can_ redesign std::list in a way that
doesn't need an externally allocated sentinel). Either way, std::list
clearly wants a usable (non-deleted) move constructor...?

For the TempDir example, I'm a proponent of 2) (make it partially
formed), which is also precisely what you need to do to make it relocatable.



> The reloc operator introduces a language-level guarantee (no use after
> relocation) that will always be better than an external tool that
> developers may simply not run.
>

I'm a very strong supporter of tooling, so I'm not really buying the
argument of "there is tooling, it solves the problem, but developers may
not run it". C++ without tooling is a doomed language. But I understand
it's a personal opinion, yet I'd like this criticism to be properly
addressed.

On a more language level, I'd say that then the question "why all the
relocation machinery, and not just an operator that moves + makes the
name unusable" should be discussed in the paper.


> > The above point wasn't my main criticism. My main criticism was that the
> > paper seems to settle on a convoluted syntax for the already existing
> > move semantics (plus the p1144 bits). Did I misunderstand something?
>
> Relocation and move semantics cover different needs. Move semantics are
> still needed (for instance to move a vector or unique_ptr and reuse them
> later). Relocation is here:
> - for classes that don't mix well with move-semantics and would benefit
> from being relocation-only (no move constructor) ;

Do you have examples of such classes?


> - for performance reasons: relocating a variable that will be touched
> again may allow for better optimization than using move-semantics ;

Ditto. As I said, I don't think I've ever encountered a class where
"safe to destroy" was less expensive than "safe to destroy and to
reassign to".


> - for a safer code: we sometimes use std::move on variables that we
> intend to never touch again. reloc enforces that.

See above.



> Besides, we can mark
> objects as const and still relocate them, which is not possible with
> move-semantics.

To be honest this could be actually also be seen as a _bad_ thing, and
therefore I'd like more motivation besides "let's just make it possible".


If I have something like

   template <typename T>
   void f(T &obj) {
     T temp = std::move(obj);
   }

and I pass in e.g. a `const unique_ptr`, this code fails to compile.
It's actually good that it fails to compile -- it's warning me that it'd
be attempting a modification on a const object.

Are you saying that if instead I use `reloc`, that code should compile?
If so, does the benefit of "I can reloc const object" truly overcome the
con of "the compiler lets me modify const objects"?


My 2 c,

-- 
Giuseppe D'Angelo

Received on 2022-05-02 11:30:23