Adrian
Adrian

Reputation: 2365

How should I write function parameters to enforce a move rather than a copy?

I want to move a large container from a return value into another class using that class' constructor. How do I formulate the parameter to ensure that it doesn't end up being copied?

/* for the sake of simplicity, imagine this typedef to be global */
typedef std::unordered_map<std::string, unsigned int> umap;

umap foo()
{
    umap m; /* fill with lots of data */
    return m;
}

class Bar
{
public:
    Bar(umap m) : bm(m) { }
private:
    umap bm;
};

Bar myBar(foo()); // run foo and pass return value directly to Bar constructor

Will above formulation trigger the appropriate behavior, or do I need to specify the constructor's parameters as rvalue-references, the way containers do for their own move-semantics?

public:
    Bar(umap&& m) : bm(m) { }

or

public:
    Bar(umap&& m) : bm(std::move(m)) { }

...?

Upvotes: 13

Views: 3211

Answers (4)

Klaus
Klaus

Reputation: 25613

In addition to the answers above:

What I see in your example is a classical case for "copy elision" optimization.

What are copy elision and return value optimization?

An actual compiler needs no support from you to optimize the things away and there is no must to deal yourself with rvalue references in this special case of return value optimization.

Upvotes: 1

user541686
user541686

Reputation: 210525

In C++11 you use std::move. If you need C++03 compatibility you can default-construct bm via :bm() and then use using std::swap; swap(bm, m);.

Upvotes: 0

mpark
mpark

Reputation: 7904

Enforce passing of rvalue into the constructor and also make sure to move the map, like this:

Bar(umap &&m) : bm(std::move(m)) {}

Starting with the first example, to the above solution:

Let's start with the first example of Bar(umap m) : bm(m) {}. This will perform at least 1 copy, maybe 2.

bm(m) will always copy. m here is lvalue even though it's bound to an rvalue. To make it move, you must wrap it in std::move to turn it back into an rvalue. We get bm(std::move(m)).

Bar(umap m) might copy. The value will be moved in if it's an rvalue, but will be copied if it's an lvalue. Since we want to prevent all copies, we need to only let rvalue bind. We get Bar(umap && m).

Upvotes: 4

Kerrek SB
Kerrek SB

Reputation: 477140

If you want to support moving and copying, the easiest way would be to pass by value:

Bar(umap m) : bm(std::move(m)) { }

Now you can construct Bar from both an lvalue and an rvalue:

umap m;
Bar b1(m);               // copies
Bar b2(std::move(m));    // moves
Bar b3(make_umap());     // moves

If you only want to support rvalues, then use an explicit rvalue reference:

Bar(umap && m) : bm(std::move(m)) { }

The std::move is always necessary, since (m) is always an lvalue.

Upvotes: 16

Related Questions