1

If I have a class that manages some dynamic memory (e.g. a vector-type class) and it already has a move-constructor, does it ever make sense to supply a move-aware overload for a function, or will the move-constructor take care of it?

For instance, which of these (if any) should I overload with a Class&& variant:

// the move-aware overload for all of these would be
// void FuncX(Class&&);

void Func1(Class);

void Func3(Class&); // doesn't make a local copy
void Func4(Class&); // makes a local copy

void Func5(Class const);

void Func7(Class const&); // doesn't make a local copy
void Func8(Class const&); // makes a local copy

Do any of them lose optimization opportunities because I am not supplying a move-aware variant?

Baruch
  • 20,590
  • 28
  • 126
  • 201
  • 1
    I'm not sure what you are asking. The two overloads you show would result in ambiguous overloads. So would `operator=(Class)` and `operator=(Class&&)`. – juanchopanza May 29 '14 at 07:52
  • The *move assignment operator* can save you a call to the *move constructor* compared to *copy and swap*. For the functions it really depends on what they are supposed to do. "Makes a local copy" is not a sufficient criteria. – nwp May 29 '14 at 07:53
  • @juanchopanza I hope my edit clears up the question – Baruch May 29 '14 at 08:03
  • That's better. Still, you cannot provide an rvalue reference overload for most of those functions because you would get ambiguous overloads. You can have `const` lvalue reference and rvalue reference overloads, or value by itself. – juanchopanza May 29 '14 at 08:07
  • @juanchopanza Why can't I have an rvalue reference and a non-const lvalue reference? – Baruch May 29 '14 at 08:16
  • That you can have (I didn't say you couldn't, at least I didn't mean to imply that.) The problem really is the "value" overload. – juanchopanza May 29 '14 at 08:19

2 Answers2

5

Func1 and Func5 have the same signature, and are already "move-aware" because they can be called with lvalues or rvalues and the argument can be move constructed when passed an rvalue.

Func3 and Func4 cannot be called with rvalues at the moment, overloading them with Func(Class&&) would be a major change in semantics, not just an optimisation.

Func7 can already be called with rvalues and there is nothing to initialize via copy or move, overloading it would be pointless.

Func8 can be called with rvalues but performs a copy, changing that function to Func8(Class) would initialize the argument using either a copy or a move, depending on whether it was called with an lvalue or rvalue. Alternatively, overloading it with Func8(Class&&) would also allow you to avoid the copy when passing an rvalue, but now you have two functions to maintain.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • If I understand your answer correctly, you are saying that other than the copy/move constructor pair, there is usually no need to explicitly overload a function for rvalue references. If I don't need a local copy I can pass by `Class const&`, and if I do then pass by value (`Class`) and I will get all the benefits of rvalue references. Is this correct? – Baruch May 29 '14 at 08:54
  • I didn't say "usually". It's impossible to generalise from a few function signatures to state a general rule like that. It depends what the functions actually _do_. But it's certainly true that passing `Class const&` works perfectly well for rvalues as well as lvalues. When you do need a local copy sometimes having separate rvalue and lvalue overloads can allow one function to use a slightly smarter implementation, so it's not always true that passing by value is better. – Jonathan Wakely May 29 '14 at 09:13
  • I think it is a pretty good **rule of thumb** to take by value when you will be making a copy anyway, take by `const &` when you don't, and take by non-const & for out-params. By this rule of thumb, the only overloading for lvalue and rvalue references is on copy/move constructor/assignment (and "universal references" in templates). I have found situations where it was useful to take full control and overload other functions on lvalue/rvalue(/const-lvalue occasionally)-ness specifically, but for me this has been the exception and the rule of thumb works well for >~98% of situations. – Adam H. Peterson May 29 '14 at 17:14
  • @baruch Not quite. I'm saying that it usually isn't worth the added complexity of adding an overload. In all but exceptional cases, pass class types by reference to const, and don't worry about it. – James Kanze May 29 '14 at 18:00
  • @AdamH.Peterson That's a very bad rule of thumb, since it will often result in two copies instead of just one. – James Kanze May 29 '14 at 18:02
  • @JamesKanze, you're going to have to connect the dots for me on that. I don't see how it introduces any additional copies. (I see how it will sometimes introduce additional moves, but I think code simplicity is worth more than a few moves, and with antialiasing optimizations, it may end up a wash anyway.) – Adam H. Peterson May 29 '14 at 18:11
  • @AdamH.Peterson You copy it once into the argument, and a second time into the final destination. That's twice. It _may_ be possible to convert one or the other, or sometimes even both, to a move, but it won't generally be the case. – James Kanze May 29 '14 at 18:43
  • @JamesKanze, I don't copy it to the final destination, though. The parameter passed-by-value is a regular local variable, and it _is_ the final destination; that copy takes the place of the copy I would have done locally. (And by taking by value, I avoid the first copy if the argument is a temporary.) – Adam H. Peterson May 29 '14 at 19:41
3

First, of course, you shouldn't bother with the move aware overload until the profiler shows it to be necessary; it's additional complexity, which shouldn't be introduced unless it is necessary.

Supposing, however, that the profiler does show copying to be a significant overhead:

Func1 (and Func5, which has the same signature): This violates most coding guidelines (which say to pass class objects by reference to const); if you use this signature, it is probably because you need a unique copy of the parameter. Move semantics will give you a unique copy, so they might be appropriate.

Func2 and Func3 imply that you are going to modify the client's object. So you need access to the client's object, which move semantics don't give you.

Func7 and Func8 are the usual case. If you're just accessing, and don't need to store a copy yourself, there's no point in move semantics; it will probably be slightly slower than the reference to const anyway. If you're storing a copy, then move semantics can make a significant difference.

James Kanze
  • 150,581
  • 18
  • 184
  • 329