Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are arguments passed via `&&` useful for non constructor functions?

One may have a function void setData(std::string arg); and call it via setData(std::move(data)); thus calling move constructor, and he would do the same for void setData(std::string && arg); (except he would be forced to move data into it). Cant compiler decide do if use of move shall be performed for simple cases, does any do it already?

So my question is: one shall use && for not only compilers but for general code (such as API members created for other developers)?

like image 304
DuckQueen Avatar asked May 19 '15 02:05

DuckQueen


2 Answers

Optimizing for r-values

Comparing void setData(std::string arg) and void setData(std::string&& arg). In the first case I assume setData moves the data into place

class Widget {
  std::string data;
 public:
  void setData(std::string data) { this->data = std::move(data); }
};

And if we called it like this

w.setData(std::move(data));

we will get one call to the move constructor to construct the function argument and one call to the move assignment operator to move the data into the member variable. So two moves in total.

But if we overload for r-value references like this:

class Widget {
  std::string data;
 public:
  void setData(std::string&& data) { this->data = std::move(data); }
};

You only get one call to the move assignment operator. You might say "But moves are cheap!" which might be true. In some cases they are not cheap (e.g std::array) and in the case of std::string most compilers implement the small string optimization (SSO) so that for small strings a move is no cheaper than a copy.

Optimizing for l-values

The argument for pass-by-value is often that you can optimize for both l-values and r-values without having to provide two overloads void setData(const std::string& arg) and void setData(std::string&& arg). So lets compare what happens if we pass an l-value to void setData(std::string arg) vs void setData(const std::string& arg). In the first case you will get one unconditional copy and then one move-assignment. In the second case you just get one assignment. The extra move-assignment is probably insignificant but it is possible that an unconditional copy is much more expensive than an assignment. If you call setData more than once on the same object the assignment may be able to re-use existing capacity and avoid a re-allocation. An unconditional copy will always have to do an allocation.

It may be that these considerations are insignificant in practice but it is worth being aware of them.

Documenting/Enforcing a transfer of ownership

The other use of r-value reference parameters is to document/enforce a transfer of ownership. Say you had some large object that is copyable and moveable then you might provide only void setData(BigData&& data) to enforce a move. As well as making it less likely that someone will accidentally make a copy, it also documents that we are taking ownership of the data. You don't necessarily need to move the whole object either, you might just steal part of the object.

like image 96
Chris Drew Avatar answered Nov 04 '22 08:11

Chris Drew


Cant compiler decide do if use of move shall be performed for simple cases, does any do it already?

Even with only void setData(std::string arg);, the compiler will move values like temporaries automatically:

x.setData(my_string + "more");  // move from temporary std::string
x.setData(get_a_string()); // move returned-by-value string

As for the utility of having a &&-overload, consider some calling code:

std::string q = get_a_string();
myObj.setData(std::move(q));
q = pick_a_string_to_copy[rand() % 10];

Here, if there's only a setData(std::string s) overload, then the the move constructor for s will take ownership of q, and any efficient implementation will leave q without any pointer to dynamically allocated free-store. Then setData() will presumably assign to the data member, which will swap with s's free-store buffer, and s will delete[] as setData returns. The next q = line above will need to allocate a new buffer for its next value (assuming size() greater than any short-string optimisation buffer).

This contrasts with the situation if there's a setData(std::string&& s) overload, where ultimately q's buffer is likely to be swapped with myobj's data member's buffer, such that q still owns dynamic memory that might just be enough to store the next value it's assigned - saving a little time. On the other hand the buffer might be bigger than needed, hogging memory for longer than needed.

Put simply, with && the caller can trade free-store buffers rather than losing a buffer or doing inefficient copying.

All up, the functional differences aren't often relevant, but they do exist.

like image 31
Tony Delroy Avatar answered Nov 04 '22 07:11

Tony Delroy