10

Here's a very easy way to define move assignment for most any class with a move constructor:

class Foo {
public:
  Foo(Foo&& foo);                     // you still have to write this one
  Foo& operator=(Foo&& foo) {
    if (this != &foo) {               // avoid destructing the only copy
      this->~Foo();                   // call your own destructor
      new (this) Foo(std::move(foo)); // call move constructor via placement new
    }
    return *this;
  }
  // ...
};

Is this sequence of calling your own destructor followed by placement new on the this pointer safe in standard C++11?

Bjarke H. Roune
  • 3,667
  • 2
  • 22
  • 26
  • 2
    Your move constructor had better be `noexcept`, or you'll attempt destroy an already-destroyed object if it throws and wander off in UB-land. – ildjarn Oct 26 '12 at 19:40
  • 1
    A good trick for move/copy assignment is to simply [take the parameter by value](http://stackoverflow.com/questions/9746748/does-it-make-sense-to-reuse-destructor-logic-by-using-stdswap-in-a-move-assign/9746772#9746772). A user will either move-construct the value parameter or copy-construct it (or elide into it). You can then use `std::swap` to swap the value into your object. – Nicol Bolas Oct 27 '12 at 00:10

2 Answers2

5

Only if you never, ever derive a type from this class. If you do, this will turn the object into a monstrosity. It's unfortunate that the standard uses this as an example in explaining object lifetimes. It's a really bad thing to do in real-world code.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • Is there also a problem with taking new-allocated memory, manually ~dtoring it, then placement newing it? I'd have to standard-delve to check that this is permitted myself. – Yakk - Adam Nevraumont Oct 26 '12 at 18:23
  • @Yakk no, since you're not [de]allocating anything. – Seth Carnegie Oct 26 '12 at 18:26
  • @Yakk no, there should be no problem in that respect so long as the object you originally allocated has size and alignment requirements sufficient for the object you create via placement new, and the pointer you pass to delete is of an appropriate type. – bames53 Oct 26 '12 at 18:29
  • If the derived class has a virtual destructor or needs to do something special for move assignment, shouldn't the move assignment be virtual and also overridden in the sub-class? In that case the derived class can just implement move assignment in the same way. If the derived class author doesn't know not to call the super-class move-assignment then there is a problem, yes - it's either this way all the way or not at all. Is this specifically your concern? – Bjarke H. Roune Oct 26 '12 at 18:45
  • Also it seems to me that classes with sub-types are often going to have a hard time supporting move assignment in the super-class. If you have two sub-types Bar and Baz and you try moving a Bar to a Baz through the Foo move assignment then things are tricky independently of this technique. I'm not sure that deriving and moving really mix all that well in general? – Bjarke H. Roune Oct 26 '12 at 19:00
  • @BjarkeH.Roune - yes, if derived types don't assume that objects work in their usual ways, it might be possible to get something that more or less hangs together. But why go to that trouble? Writing a move assignment operator isn't very hard, and this approach to avoiding it sets traps for maintainers and users. – Pete Becker Oct 26 '12 at 19:19
  • class Bar:Foo { int x; }; Foo* f = new Bar(); *f = Foo(); delete f;-- we allocate a Bar, destroy it as a ~Foo, allocate a Foo in its place, then delete it as a Bar. I'd be surprised if this wasn't undefined behavior. – Yakk - Adam Nevraumont Oct 26 '12 at 19:23
  • @Yakk: I suspect actually the second delete will invoke Foo's destructor — if the program gets that far. The first delete is definitely wrong however. – dhardy Feb 05 '13 at 13:19
  • @PeteBecker or anybody really, could you please elaborate why deriving from this class "will turn the object into a monstrosity"? I understand one can run into slicing issues, but that's not due to using this technique per se. Or did I miss something? – Super-intelligent Shade Sep 14 '18 at 21:47
  • 1
    @InnocentBystander — add a virtual function, and override it in a derived class. Now (too much implementation detail here) the constructor call in that assignment operator will replace the derived type’s vtable pointer with the base type’s vtable pointer. Voila, object of derived type but virtual calls don’t go to their overriders. – Pete Becker Sep 14 '18 at 22:56
  • @PeteBecker hmm, I see what you did there. So, in order for this to "work", every derived class would have to (a) call its dtor, (b) call parent class' move oper=, and (c) call its placement new. Yeah, that's pretty monstrous alright. – Super-intelligent Shade Sep 15 '18 at 01:10
0

Technically, the source code is safe in this tiny example. But the reality is that if you ever even look at Foo funny, you will invoke UB. It's so hideously unsafe that it's completely not worth it. Just use swap like everybody else- there's a reason for it and it's because that's the right choice. Also, self-assignment-checking is bad.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 2
    Copy and swap is not always the best implementation. It's one easy way of getting the strong exception safety guarantee, but there are trade-offs. Howard Hinnant discusses it some in part of [this](http://stackoverflow.com/a/6687520/365496) answer. – bames53 Oct 26 '12 at 18:38
  • Is your concern mainly with a derived class author (erroneously in this case) calling the Foo move assignment, or are there more problems than that? This technique does require sub-class authors to override virtually and use the technique too. Most problems I can think of beyond that involve the move assignment not being virtual and then the answer just is that the move assignment should be virtual in those cases regardless of whether this technique is used (do you agree?). Do you have concerns beyond that? – Bjarke H. Roune Oct 26 '12 at 18:49