Неявное перемещение против операций копирования и локализации - PullRequest
3 голосов
/ 06 февраля 2020

Я изо всех сил пытаюсь понять неявные операции перемещения, когда в классе есть член, операции перемещения которого не были определены:

int main() {
    struct A // no move: move = copy
    {
        A() = default;
        A(const A&) {
            cout << "A'copy-ctor\n";
        };
        A& operator=(const A&) {
            cout << "A'copy-assign\n";
            return *this;
        }
    };

    struct B
    {
        B() = default;
        A a; // does this make B non-moveable?
        unique_ptr<int> upi;
        // B(B&&) noexcept = default;
        // B& operator=(B&&)noexcept = default;
    };

    A a;
    A a2 = std::move(a); // ok use copy ctor instead of move one
    a2 = std::move(a); // ok use copy assignment instead of move one

    B b;
    B b2 = std::move(b); // why this works?
    b = std::move(b2); // and this works?
    // b = b2; // error: copy deleted because of non-copyable member upi

    cout << "\nDone!\n";
}

Так что я вижу, что A является неподвижным классом из-за определение его операций управления копированием, чтобы его можно было только скопировать, и при любой попытке перемещения объекта этого класса вместо этого используется соответствующая операция копирования.

До тех пор, пока здесь все в порядке, если я прав. Но B имеет объект, не способный к копированию upi, который является unique_ptr, поэтому операции копирования определяются как удаленные функции, поэтому мы не можем копировать объекты этого класса. Но у этого класса есть неподвижный объект a, поэтому я думаю, что этот класс (B) не является ни копируемым, ни подвижным. Но почему инициализация b2 и присвоение b работает нормально? Что именно происходит?

B b2 = std::move(b); // ok?!

Почему строка выше вызывает конструктор копирования класса A и вызывает ли он конструктор перемещения B?

  • Все становится еще хуже для меня: если я раскомментирую строки операций перемещения в B, приведенная выше инициализация не скомпилирует жалобу на ссылку на удаленную функцию, то же самое для назначения!

Может кто-нибудь помочь мне чем? бывает точно? Я гуглил и читал в cppreference и на многих сайтах, прежде чем опубликовать вопрос здесь.

Вывод:

A'copy-ctor
A'copy-assign
A'copy-ctor
A'copy-assign

Done!

Ответы [ 3 ]

4 голосов
/ 06 февраля 2020

Имейте в виду, что означает «перемещать» данные в C ++ (при условии, что мы следуем обычным соглашениям). Если вы переместите объект x в объект y, то y получит все данные, которые были в x, а x - это ... ну, нам все равно, что такое x, пока это все еще действует для уничтожения. Часто мы думаем о x как о потере всех своих данных, но это не обязательно. Все, что требуется, это то, что x является действительным. Если x заканчивается теми же данными, что и y, нам все равно.

Копирование x в y заставляет y получать все данные, которые были в x и x остается в допустимом состоянии (при условии, что операция копирования выполняется в соответствии с соглашениями и не содержит ошибок). Таким образом, копирование считается движением. Причина определения операций перемещения в дополнение к операциям копирования заключается не в том, чтобы разрешить что-то новое, а в некоторых случаях для повышения эффективности. Все, что может быть скопировано, может быть перемещено, если вы не предпримете шаги для предотвращения перемещений.

Так что я вижу, что A является неподвижным классом из-за определения его операций управления копированием, поэтому он может быть скопировано только и любая попытка переместить объект этого класса, вместо этого используется соответствующая операция копирования.

Я вижу, что A является подвижным классом (несмотря на отсутствие конструктора перемещения и назначения перемещения) из-за определения его операций управления копированием. Любая попытка переместить объект этого класса приведет к соответствующей операции копирования. Если вы хотите, чтобы класс был копируемым, но не перемещаемым, вам нужно удалить операции перемещения, сохранив при этом копии. (Попробуйте. Добавьте A(A&&) = delete; к определению A.)

Класс B имеет одного члена, который можно перемещать или копировать, и одного члена, который может быть перемещено, но не скопировано. Таким образом, B может быть перемещен, но не скопирован. При перемещении B элемент unique_ptr будет перемещен, как вы ожидаете, и элемент A будет скопирован (запасной вариант для движущихся объектов типа A).


Ситуация для меня становится еще хуже: если я раскомментирую строки операций перемещения в B, приведенная выше инициализация не скомпилирует жалобу на ссылку на удаленную функцию, то же самое для назначения!

Внимательно прочитайте сообщение об ошибке. Когда я реплицировал этот результат, за ошибкой «использование удаленной функции» последовала заметка с более подробной информацией: конструктор перемещения был удален, поскольку «его спецификация исключений не соответствует неявной спецификации исключений». Удаление ключевых слов noexcept позволило скомпилировать код (используя g cc 9.2 и 6.1).

Кроме того, вы можете добавить noexcept в конструктор копирования и назначить копирование A (сохраняя noexcept на ходу операции B). Это один из способов продемонстрировать, что операции перемещения по умолчанию B используют операции копирования A.

2 голосов
/ 06 февраля 2020

Вот краткое изложение превосходного ответа @ JaMiT:

Класс A является перемещаемым через его конструктор копирования и оператор назначения копирования, даже если класс A не является MoveConstructible и не MoveAssignable. См. Примечания на страницах cppreference.com для MoveConstructible и MoveAssignable .

И, таким образом, класс B также является подвижным.

Язык позволяет предотвратить подвижность для класса A путем явного удаления или удаления конструктора перемещения и присвоения перемещения, даже если класс A является все еще копируемый.

Есть ли практическая причина иметь копируемый, но не перемещаемый класс? Кто-то задавал именно этот вопрос несколько лет go здесь . Ответы и комментарии изо всех сил пытались найти какую-либо практическую причину, чтобы хотеть копируемый, но не перемещаемый класс.

0 голосов
/ 06 февраля 2020

std :: move не заставляет объект копироваться. Он просто возвращает && - ссылку (что позволяет компилятору использовать оператор перемещения ctor / assign).
В случаях, когда 1,2 копируется объект.
В 3,4 случаях (я думаю) объект перемещается. Но A все еще копируется, потому что его нельзя переместить.

...