Как реализация конструктора перемещения влияет на оптимизацию возвращаемого значения?

Рассмотрим следующий фрагмент кода:

#include <iostream>
#include <string>

class A { 
 public:
    A() { 
        std::cout << "A::A()\n";
    } 

    ~A() { 
        std::cout << "A::~A()\n";
    } 

    A(const A&) = delete;

    A(A&&) { 
        std::cout << "A::A(A&&)\n";
    };

};

A f() { 
    A a;
    return a;
}

int main() { 
    A a = f();
    return 0;
}

Он отлично компилируется с g++ и clang++ и выводит

A::A()
A::~A()

Похоже, что в этом случае срабатывает RVO. Обратите внимание, что конструктор перемещения не вызывается.

Однако, если удалить этот неиспользуемый конструктор перемещения из приведенного выше кода, фрагмент кода превратится в это:

#include <iostream>
#include <string>

class A { 
 public:
    A() { 
        std::cout << "A::A()\n";
    } 

    ~A() { 
        std::cout << "A::~A()\n";
    } 

    A(const A&) = delete;

};

A f() { 
    A a;
    return a;
}

int main() { 
    A a = f();
    return 0;
}

И clang++, и g++ отказываются компилировать это, потому что конструктор-копия класса A помечен как удаленный, поэтому кажется, что RVO не происходит.

Как к этому может привести удаление неиспользуемого конструктора перемещения?


person Anton K    schedule 01.06.2016    source источник
comment
Компилируется нормально для меня. Использование Apple LLVM version 7.3.0 (clang-703.0.31). Также ideone.com/xTpD56   -  person miguel.martin    schedule 01.06.2016
comment
Если вы объявляете конструктор копирования, конструктор перемещения вообще не объявляется неявно.   -  person Kerrek SB    schedule 01.06.2016


Ответы (3)


Обратите внимание, что при оптимизации copy elision конструктор копирования/перемещения по-прежнему должен присутствовать и быть доступным. И не гарантируется, что удаление копии будет выполнено в каждом случае.

(выделено мной)

Даже когда происходит удаление копии и конструктор копирования/перемещения не вызывается, он должен присутствовать и быть доступным (как если бы оптимизации вообще не было), иначе программа будет неправильно сформирована.

Для вашего кода конструктор копирования был deleteed, и если вы удалите определение конструктора перемещения, и он не будет неявно объявлен, поскольку класс A имеет определяемый пользователем деструктор, то оба конструктора перемещения/копирования отсутствуют и доступен, поэтому компиляция не удалась.

Таким образом, здесь необходим синтаксис копирования/перемещения, и компилятор проверит его. Затем компилятор решит, выполнять копирование или нет (например, в режиме отладки).

Кстати: вы можете использовать -fno-elide-constructors с clang и gcc, чтобы запретить это.

person songyuanyao    schedule 01.06.2016
comment
Я не понимаю, как компилятор выбирает, какой конструктор (копировать или перемещать) использовать для возврата локальной функции перед применением rvo. Это определено? - person Anton K; 01.06.2016
comment
Компилятор @Anton использует лучшее совпадение. Если значение является значением r, и команда перемещения доступна, она будет вызвана. Если это не rvalue или конструктор перемещения недоступен, будет использоваться копирующий ctor. В этом отношении он ничем не отличается от разрешения перегрузки для обычных функций. - person SergeyA; 01.06.2016
comment
@Anton Да, какой из них будет подобран, хорошо определено. Для вашего кода будет выбран move ctor, потому что f() вернет temporary, который может быть привязан к ссылке rvalue. Во всяком случае, это, кажется, другая проблема. - person songyuanyao; 01.06.2016

Вы должны иметь в виду, что (N)RVO — это оптимизация. Даже если он сработает, код должен соответствовать стандарту, в котором говорится, что значение создается с использованием конструктора копирования (или перемещения). Даже если конструктор в конечном итоге не вызывается, он должен быть доступен.

Есть предложение разрешить отсутствующие/недоступные конструкторы, если они не будут вызываться из-за оптимизации, но я сомневаюсь, что это будет реализовано.

person SergeyA    schedule 01.06.2016
comment
Означает ли это, что в первом фрагменте локальный a из функции f возвращается семантикой перемещения, а затем оптимизатор решает использовать rvo и бросает вызов для перемещения-конструктора? - person Anton K; 01.06.2016
comment
Трудно сказать, что происходит в компиляторе в первую очередь, а во вторую :) Но сначала код должен быть правильным, а уже потом оптимизированным. - person SergeyA; 01.06.2016
comment
Перефразируем: первый отрезанный код корректен, потому что ctor перемещения определен и доступен, что позволяет вернуть локальный a из f? - person Anton K; 01.06.2016
comment
@Антон, верно. При возврате из локальных значений (или аргументов) они рассматриваются как временные. - person SergeyA; 01.06.2016

Если у вас есть конструктор копирования, у вас также должен быть конструктор перемещения, если я правильно помню.

person user6410632    schedule 01.06.2016
comment
Это комментарий (и неправильный) в лучшем случае. Ни в коем случае не ответ. - person SergeyA; 01.06.2016