Search code examples
c++movervalue

Which of these is the proper usage of std::move


I'm trying to change my code to take a vector by value using std::move instead of passing it by reference because I've gathered that would be more efficient. I've seen different ways of doing this though, one having the constructor pass by value and using std::move in the constructor, and the other way being to initialize the class with an std::move and having the constructor take an rvalue (did I get that right?). Some example below:

method 1:

Constructor:

StatisticsCompiler::StatisticsCompiler(std::vector<Wrapper<StatisticsMC>> Inner_) :Inner(std::move(Inner_))
{
}

in main:

vector<Wrapper<StatisticsMC>> gathererArray{ meanGatherer, absQuantileGatherer, relVaRGatherer, relESGatherer };
StatisticsCompiler gathererCombiner(gathererArray);

method 2.

Constructor:

StatisticsCompiler::StatisticsCompiler(std::vector<Wrapper<StatisticsMC>>&& Inner_) :Inner(Inner_)
{
}

main:

vector<Wrapper<StatisticsMC>> gathererArray{ meanGatherer, absQuantileGatherer, relVaRGatherer, relESGatherer };
StatisticsCompiler gathererCombiner(std::move(gathererArray));

Is there a difference between what's going on here or is it the same thing, the first method "looks" nicer in main but the second method is the way I would intuitively understand it to work from learning about rvalues. If performance wise they're the exact same, then what's standard practice?


Solution

  • StatisticsCompiler::StatisticsCompiler(std::vector<Wrapper<StatisticsMC>> Inner_) :Inner(std::move(Inner_))
    

    This constructor takes the argument by value. The parameter can either be copy constructed from an lvalue argument, or move constructed from an rvalue. This ability to choose between copying from lvalue and moving from rvalue, combined with the simplicity, is why this approach is recommended.

    The member is always moved from that copied or moved argument.

    StatisticsCompiler gathererCombiner(gathererArray);
    

    You passed an lvalue; therefore the parameter is copied. You could use the move constructor instead by passing an rvalue:

    StatisticsCompiler gathererCombiner(std::move(gathererArray));
    

    Or you could even use a prvalue:

     StatisticsCompiler gathererCombiner({
        meanGatherer,
        absQuantileGatherer,
        relVaRGatherer,
        relESGatherer,
     });
    

    StatisticsCompiler::StatisticsCompiler(std::vector<Wrapper<StatisticsMC>>&& Inner_) :Inner(Inner_)
    

    This constructor accepts only rvalue arguments. This is not as flexible as the first suggestion which also can accept lvalues.

    This approach always copies the parameter into the member. You might want to move instead:

    Inner(std::move(Inner_))
    

    Conclusion: When you want to store an object given as an argument, then passing by value (the first example) is good default choice. If you don't need the passed argument afterwards, then you can choose to move from it.