Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I always move on `sink` constructor or setter arguments?

Tags:

struct TestConstRef {
    std::string str;
    Test(const std::string& mStr) : str{mStr} { }
};

struct TestMove {
    std::string str;
    Test(std::string mStr) : str{std::move(mStr)} { }
};

After watching GoingNative 2013, I understood that sink arguments should always be passed by value and moved with std::move. Is TestMove::ctor the correct way of applying this idiom? Is there any case where TestConstRef::ctor is better/more efficient?


What about trivial setters? Should I use the following idiom or pass a const std::string&?

struct TestSetter {
    std::string str;
    void setStr(std::string mStr) { str = std::move(str); }
};
like image 976
Vittorio Romeo Avatar asked Sep 07 '13 13:09

Vittorio Romeo


2 Answers

The simple answer is: yes.


The reason is quite simple as well, if you store by value you might either need to move (from a temporary) or make a copy (from a l-value). Let us examine what happens in both situations, with both ways.

From a temporary

  • if you take the argument by const-ref, the temporary is bound to the const-ref and cannot be moved from again, thus you end up making a (useless) copy.
  • if you take the argument by value, the value is initialized from the temporary (moving), and then you yourself move from the argument, thus no copy is made.

One limitation: a class without an efficient move-constructor (such as std::array<T, N>) because then you did two copies instead of one.

From a l-value (or const temporary, but who would do that...)

  • if you take the argument by const-ref, nothing happens there, and then you copy it (cannot move from it), thus a single copy is made.
  • if you take the argument by value, you copy it in the argument and then move from it, thus a single copy is made.

One limitation: the same... classes for which moving is akin to copying.

So, the simple answer is that in most cases, by using a sink you avoid unnecessary copies (replacing them by moves).

The single limitation is classes for which the move constructor is as expensive (or near as expensive) as the copy constructor; in which case having two moves instead of one copy is "worst". Thankfully, such classes are rare (arrays are one case).

like image 164
Matthieu M. Avatar answered Oct 13 '22 20:10

Matthieu M.


A bit late, as this question already has an accepted answer, but anyways... here's an alternative:

struct Test {
    std::string str;
    Test(std::string&& mStr) : str{std::move(mStr)} { } // 1
    Test(const std::string& mStr) : str{mStr} { } // 2
};

Why would that be better? Consider the two cases:

From a temporary (case // 1)

Only one move-constructor is called for str.

From an l-value (case // 2)

Only one copy-constructor is called for str.

It probably can't get any better than that.

But wait, there is more:

No additional code is generated on the caller's side! The calling of the copy- or move-constructor (which might be inlined or not) can now live in the implementation of the called function (here: Test::Test) and therefore only a single copy of that code is required. If you use by-value parameter passing, the caller is responsible for producing the object that is passed to the function. This might add up in large projects and I try to avoid it if possible.

like image 29
Daniel Frey Avatar answered Oct 13 '22 20:10

Daniel Frey