Почему перемещение необходимо с emplace_back в этом примере?

Следующий минимальный рабочий пример компилируется, когда используется код в опции 1 или 2, но не компилируется, когда используется код в опции 3. Я предполагал, что emplace_back() неявно использует / вызывает move конструктор, так почему явный move() необходимо? Это как-то связано с r-value против l-value? Или это связано с std::unique_ptr нужно передать право собственности? (Я все еще новичок в этих понятиях, особенно в этом контексте.)

Для полноты, вариант 4 с push_back() тоже не компилируется, если только move() называется.

#include <iostream>
#include <vector>
#include <memory>

class Beta {
public:
    Beta(int x, int y, int z): mX(x), mY(y), mZ(z) { };
    int mX; int mY; int mZ;

};

class Alpha {
public:
    std::vector<std::unique_ptr<Beta>> betaVec;
    void addBeta(int x, int y, int z) {
        // only choose one of the following options:
        // option 1 (compiles)
        std::unique_ptr<Beta> pBeta = std::make_unique<Beta>(x, y, z);
        betaVec.emplace_back(move(pBeta));

        // option 2 (compiles)
        betaVec.emplace_back(std::make_unique<Beta>(x, y, z));

        // option 3 (does not compile)
        std::unique_ptr<Beta> pBeta = std::make_unique<Beta>(x, y, z);
        betaVec.emplace_back(pBeta);

        // option 4 (does not compile)
        std::unique_ptr<Beta> pBeta = std::make_unique<Beta>(x, y, z);
        betaVec.push_back(pBeta);

        // option 5 (compiles)
        std::unique_ptr<Beta> pBeta = std::make_unique<Beta>(x, y, z);
        betaVec.push_back(move(pBeta));
    }
};

int main() {

    return 0;
}

Примечание: я не считаю, что это дубликат этого вопроса о прохождении unique_ptr параметров функций, даже если ответы на связанный вопрос полезны, так как здесь требуется определить unique_ptr внутри функции, а затем переместить его в член vector так что он не будет уничтожен в конце функции и, кроме того, конкретно спрашивает о emplace_back() в данном контексте.

Кроме того, я думаю, что было бы полезно дать объяснения в этом контексте, поскольку иногда бывает трудно перевести объяснения из одного контекста в другой. Спасибо!

1 ответ

Решение

Я предполагал, что emplace_back() неявно использует / вызывает конструктор перемещения

Извините, но вы предполагаете, что это неправильно. emplace_back создает объект в векторе на месте, то есть вместо того, чтобы копировать / перемещать объект из его параметров, он создает элемент напрямую, что позволяет избежать конструктора копирования / перемещения.

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

так почему явный move() необходимо

Потому что вы не можете скопировать std::unique_ptr, В принципе, emplace_back делает что-то похожее на это:

new (place) T(std::forward<Ts>(args)...);

Это как если бы вы сделали: T a(std::forward<Ts>(args)...) (только для строительства это не делает то же самое).

Теперь это может быть немного более очевидно:

T option1(std::move(pBeta)); // ok, move
T option3(pBeta); // error, copy

Это как-то связано с r-value против l-value? Или это связано с std::unique_ptr нужно передать право собственности?

Ну, в некотором смысле, да. std::unique_ptr требует явной передачи права собственности, поэтому копия отключена, а перемещение - нет (вы все еще хотите передать право собственности! И копия может произойти везде - почему std::auto_ptr устарела, затем удалена). По умолчанию rvalue использует семантику перемещения, а lvalue - нет. Используя std::moveвы делаете преобразование из lvalue в prvalue, эффективно "скрывая" тот факт, что у вас есть lvalue, и компилятор с радостью от него уйдет.

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