Как обеспечить движение, не мешая РВО? - PullRequest
6 голосов
/ 29 апреля 2019

При возврате объекта из функции может произойти один из следующих случаев, начиная с C ++ 11, при условии, что определены конструкторы перемещения и копирования (см. Также примеры в конце этого поста):

  1. it подходит для copy-elision , и компилятор выполняет RVO.
  2. он соответствует для copy-elision, а компилятор не выполняет RVO, но затем ...
  3. it соответствует использованию конструктора перемещения и перемещается.
  4. ничего из вышеперечисленного и конструктор копирования не используется.

Совет дляпервые 3 случая - не использовать явный std::move, потому что перемещение выполняется в любом случае и может предотвратить возможное RVO, см., например, этот SO-post .

Однако в 4. case явное std::move улучшит производительность.Но как человек, который свободно читает ни стандартный, ни получающийся ассемблер, требуется много времени, чтобы различать случаи 1-3 и 4.

Таким образом, мой вопрос: Есть лиспособ обработки всех вышеперечисленных случаев унифицированным способом, таким образом:

  1. RVO не затрудняется (случай 1)
  2. , если RVO являетсяне выполняется, используется конструктор перемещения (случаи 2,3 и 4)
  3. , если конструктор перемещения отсутствует, в качестве запасного варианта следует использовать конструктор копирования.

Вот несколько примеров, которые также можно использовать в качестве тестовых случаев.

Во всех примерах используется следующее определение класса помощника:

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

1.пример: 1.case, выполнено RVO, живая демонстрация , результирующий ассемблер :

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

2.пример: 1.case, выполнено RVO, живая демонстрация , результирующий ассемблер :

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

3.пример: 2.case, соответствует разрешению копирования, RVO не выполнен, live-демонстрация , результирующий ассемблер :

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

4.пример: 3.case, не соответствует разрешению копирования (x - это параметр-функция), но для перемещения live-демонстрация , результирующий ассемблер :

A callee4(A x){
    return x; 
}

5.пример: 4.case, без копирования или неявного перемещения (см. SO-post ), live-демонстрация , результирующий ассемблер :

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

6.пример: 4.case, без копирования или неявного перемещения, live-демонстрация , результирующий ассемблер :

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

1 Ответ

3 голосов
/ 29 апреля 2019

Когда нельзя выполнить RVO?

Компилятор (обычно) не может выполнить RVO, если применимо любое из следующего:

  1. , который Возвращаемая локальная переменная зависит от условного (с локальными переменными, определяемыми перед условным, а не внутри него)
  2. вы возвращаете член класса, объединения или структуры
  3. , которую выРазыменование указателя для получения возвращаемого значения (включая индексирование массива)

В случае 1 вы должны либо использовать std::move, либо написать код, используя оператор if вместо троичногооператор.В случае 2 вы должны использовать std::move.В случае 3 вы также должны явно использовать std::move.

Политика обеспечения построения перемещения (если возможно)

Обработка варианта 1. Мы можем обеспечить перемещение значения, полагаясь на операторы if для возврата локальных переменных,вместо троичного оператора:

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

Если вы действительно предпочитаете использовать троичный оператор, явно используйте std::move для обоих условий:

A whichOne(bool which) {
    A a(0); 
    A b(1); 
    return which ? std::move(a) : std::move(b); 
}

Что если я только хочупереместить a, но не b? Мы можем обработать этот случай, явно построив его в условном выражении.В этом случае построенное значение гарантированно подвергнется RVO, так как обе стороны троичного числа дают prvalue .

A whichOne(bool which) {
    A a(0);
    A b(1); 
    return which 
      ? A(std::move(a)) // prvalue move-constructed from a
      : A(b);           // prvalue copy-constructed from b
}

Обработка случаев 2 и 3. Это довольно просто: используйте std::move при возврате члена объекта или при возврате ссылки, полученной из массиваили указатель.

...