Почему для этого требуется явно std:: move?

Скажем, у меня есть класс Foo, содержащий std::vector, построенный из объектов std::unique_ptr другого класса, Bar.

typedef std::unique_ptr<Bar> UniqueBar;

class Foo {
    std::vector<UniqueBar> bars;
public:
    void AddBar(UniqueBar&& bar);
};

void Foo::AddBar(UniqueBar&& bar) {
    bars.push_back(bar);
}

Это приводит к ошибке компиляции (в g++ 4.8.1), говорящей, что конструктор копирования std::unique_ptr удален, что является разумным. Вопрос здесь в том, что аргумент bar уже является ссылкой rvalue, почему вместо конструктора перемещения вызывается конструктор копирования std::unique_ptr?

Если я явно вызываю std::move в Foo::AddBar, то проблема компиляции исчезает, но я не понимаю, почему это необходимо. Я думаю, что это довольно избыточно.

Итак, что мне не хватает?

Ответ 1

В принципе, каждый объект, имеющий имя, является lvalue. Когда вы передаете объект функции, используя ссылку rvalue, функция фактически видит lvalue: она названа. Однако ссылка на rvalue указывает на то, что она исходила из объекта, который готов к передаче.

Иными словами, ссылки rvalue являются асимметричными:

  • они могут получать только rvalues, т.е. временные объекты, объекты, которые должны уйти, или объекты, которые выглядят так, как будто они являются rvalues ​​(например, результат std::move(o))
  • сама ссылка rvalue выглядит, как lvalue

Ответ 2

Сбивая с толку, как это может показаться, ссылка rvalue привязывается к rvalue, но используется как выражение lvalue.

Ответ 3

bar на самом деле является lvalue, поэтому вам нужно передать его через std::move, так что он будет отображаться как rvalue в вызове push_back.

Перегрузка Foo::AddBar(UniqueBar&& bar) просто гарантирует, что эта перегрузка будет выбрана, когда rvalue будет передано при вызове Foo::AddBar. Но сам аргумент bar имеет имя и является lvalue.

Ответ 4

bar определяется как rvalue-reference, но его категория-значение - lvalue. Это происходит потому, что у объекта есть имя. Если у него есть имя, это значение lvalue. Поэтому явный std::move необходим, потому что намерение состоит в том, чтобы избавиться от имени и вернуть значение xvalue (eXpiring-rvalue).