Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::move( ) calls copy-ctor in the absence of a move-ctor. Why and how to prevent it?

I want to know if there is a safe programming practice that would alert a coder to this subtle behavior when it takes place or, even better, avoid it in the first place.

A user of struct A might not realize there is no move constructor. In their attempt to call the absent ctor they get neither a compiler warning or any run-time indication that a copy ctor was called instead.

An answer below explains the conversion that takes place but I don't see any rationale for this being a good thing. If the constructor taking a const reference as a parameter were missing there would be a compile time error rather than just resolving to the non-const reference version. So why wouldn't an attempt at using move semantics result in a compile time error when there is no move semantics implemented in the class?

Is there a way to avoid this behavior with some compile time option or at least a way to detect it during run time?

One could assert(source is null) after the move if they were anticipating the problem but that is true of so many problems.

Example, when:

struct A {
    A() {...}
    A(A &a) {...}
    A(A const & A) {...}
};

is constructed as in:

A a1;
A a2 = std::move(a1);  //calls const copy (but how would I know?)

this results in the const version of the copy ctor being called. Now two objects might have a pointer to a single resource while one of them is likely to be calling it's destructor soon.

like image 679
Arbalest Avatar asked Aug 16 '13 00:08

Arbalest


2 Answers

Since std::move returns an rvalue, which is convertible to a const ref that is why the copy ctor is being silently called. You can fix this several ways.

  1. Easy way, if your class has no dynamic allocation just use the default mctor like this.
    A(A &&) = default;
  2. get rid of the const in the cctor, prolly not a good idea but it won't compile than

  3. Just implement your own move constructor, why are you moving to an object if it has no mctor


This is slightly off topic but you can also qualify a member function to only work with a modifiable lvalue like this.

int func()&;

If you are dealing with legacy code here is compile time check for move constructable

like image 156
aaronman Avatar answered Sep 21 '22 04:09

aaronman


There isn't a safe programming practice that would alert to such a thing, because doing a copy after std::move is incredibly common and normal. Adding a warning would cause pretty much everything in <algorithm> to start firing warnings, and we'd need a whole new function that we'd use that works exactly like std::move does now. A lot of algorithms rely on copying if there is no move constructor, or moving otherwise, and that is what std::move is for. At best you should be arguing for the existence of a std::force_move.

Secondly, it's completely unnecessary. Take this modified version of the code you showed.

void legacy_api(A a1) {
   A a2 = std::move(a1);
   ...

You say it's a problem that it subtly used an expensive copy rather than a subtle move. I disagree, what it needed was a new instance of the object, potentially destroying the previous. If the code needs another instance, then it needs another instance. Whether or not it destroyed the previous should be completely irrelevant. If the code is moving when it didn't need another instance, well then the lagacy API is clearly pooly written and the aformentioned warning won't help that. I can't think of any reason a function would require a move but no copy, there's no purpose for such a thing.

Finally, "Now two objects might have a pointer to a single resource while one of them is likely to be calling it's destructor soon." No, if that happens then that means that A's copy constructor has a bug, period. Fix the bugs in your code, and the problems go away. Like magic!

like image 26
Mooing Duck Avatar answered Sep 23 '22 04:09

Mooing Duck