Неявное перемещение против операций копирования и сдерживания

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

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 ответа

Решение

Не забывайте, что означает "перемещать" данные в 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 Ключевые слова позволяют компилировать код (с использованием gcc 9.2 и 6.1).

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

Вот краткое изложение отличного ответа @JaMiT:

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

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

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

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

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

Другие вопросы по тегам