lundi 29 avril 2019

How to ensure moving without impeding RVO?

When returning an object from a function one of the following cases can happen since C++11, assuming move- and copy-constructors are defined (see also the examples at the end of this post):

  1. it qualifies for copy-elision and compiler performs RVO.
  2. it qualifies for copy-elision and compiler doesn't perform RVO, but then ...
  3. it qualifies for the usage of move constructor and is moved.
  4. none of the above and the copy-constructor is used.

The advice for the first 3 cases is not to use the explicit std::move, because move is performed anyway and could prevent possible RVO, see for example this SO-post.

However, in the 4. case an explicit std::move would improve the performance. But as somebody who is fluent in reading neither the standard nor the resulting assembler, it takes a lot of time to differentiate between cases 1-3 and 4.

Thus my question: Is there are a way to handle all of the above cases in an unified manner, such that:

  1. RVO is not impeded (case 1)
  2. if RVO is not performed, move-constructor is used (cases 2,3 and 4)
  3. if there is no move-constructor, copy-constructor should be used as fallback.

Here are some examples, which also can be used as test cases.

All examples use the following helper-class-definition:

struct A{
    int x;
    A(int x_);
    A(const A& a);
    A(const A&& a);
    ~A();
};

1. example: 1.case, RVO performed, live-demonstration, resulting assembler:

A callee1(){
    A a(0);
    return a;
}

2. example: 1.case, RVO performed, live-demonstration, resulting assembler:

A callee2(bool which){
    return which? A(0) : A(1);
}

3. example: 2.case, qualifies for copy-elision, RVO not performed, live-demonstration, resulting assembler:

A callee3(bool which){
    A a(0);
    A b(1);
    if(which)
      return a;
    else
      return b; 
}

4. example: 3.case, doesn't qualify for copy-elision (x is a function-parameter), but for moving, live-demonstration, resulting assembler:

A callee4(A x){
    return x; 
}

5. example: 4.case, no copy-elision or implicit moving (see this SO-post), live-demonstration, resulting assembler:

A callee5(bool which){
    A a(0);
    A b(1);
    return which ? a : b;
}

6. example: 4.case, no copy-elision or implicit moving, live-demonstration, resulting assembler:

A callee6(){
    std::pair<A,int> x{0,1};
    return x.first;
}

Aucun commentaire:

Enregistrer un commentaire