Почему мы можем использовать std :: move для объекта const?

113

В C ++ 11 мы можем написать такой код:

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

когда я звоню std::move, это означает, что я хочу переместить объект, т.е. я изменю объект. Перемещать constобъект нецелесообразно, так почему бы std::moveне ограничить такое поведение? В будущем это будет ловушка, верно?

Здесь ловушка означает, как Брэндон упомянул в комментарии:

«Я думаю, он имел в виду, что это« ловушка »его подлый подлый, потому что, если он не осознает, он заканчивает с копией, которая не то, что он намеревался».

В книге Скотта Мейерса «Эффективный современный C ++» он приводит пример:

class Annotation {
public:
    explicit Annotation(const std::string text)
     : value(std::move(text)) //here we want to call string(string&&),
                              //but because text is const, 
                              //the return type of std::move(text) is const std::string&&
                              //so we actually called string(const string&)
                              //it is a bug which is very hard to find out
private:
    std::string value;
};

Если бы std::moveбыло запрещено работать с constобъектом, мы могли бы легко обнаружить ошибку, верно?

Камино
источник
2
Но попробуйте сдвинуть его. Попробуйте изменить его состояние. std::moveсам по себе ничего не делает с объектом. Можно поспорить std::move, плохо назван.
juanchopanza
3
На самом деле он ничего не перемещает . Все, что он делает, приводится к ссылке rvalue. попробуйте CAT cat2 = std::move(cat);, предполагая, что CATподдерживает регулярное присвоение перемещений.
WhozCraig
11
std::moveэто всего лишь гипс, он на самом деле ничего не двигает
Red Alert
2
@WhozCraig: Осторожно, так как опубликованный вами код компилируется и выполняется без предупреждения, что вводит в заблуждение.
Mooing Duck
1
@MooingDuck Никогда не говорил, что он не компилируется. он работает только потому, что по умолчанию включен copy-ctor. Приглушите это, и колеса отвалятся.
WhozCraig

Ответы:

55
struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

здесь мы видим использование std::moveфайла T const. Он возвращает T const&&. У нас есть конструктор перемещения, strangeкоторый принимает именно этот тип.

И это называется.

Это правда, что этот странный тип встречается реже, чем ошибки, которые ваше предложение исправит.

Но, с другой стороны, существующий std::moveлучше работает в общем коде, где вы не знаете, с каким типом вы работаете: a Tили T const.

Якк - Адам Неврамонт
источник
3
+1 за то , что первым ответ , который на самом деле попытка объяснить , почему вы хотите позвонить std::moveна constобъекте.
Крис Дрю
+1 для демонстрации взятия функции const T&&. Это выражает «протокол API» типа «я возьму rvalue-ref, но обещаю, что не буду его изменять». Думаю, если не считать использования mutable, это необычно. Возможно, еще один вариант использования - иметь возможность использовать forward_as_tupleпрактически все, а затем использовать это.
F Pereira,
107

Вы упускаете из виду одну хитрость, а именно, что на std::move(cat) самом деле ничего не движется . Он просто говорит компилятору попытаться переместиться. Однако, поскольку в вашем классе нет конструктора, который принимает a const CAT&&, вместо этого он будет использовать неявный const CAT&конструктор копирования и безопасно копировать. Ни опасности, ни ловушки. Если конструктор копирования по какой-либо причине отключен, вы получите ошибку компилятора.

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

печатает COPY, нет MOVE.

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

Обратите внимание, что ошибка в указанном вами коде связана с производительностью , а не со стабильностью , поэтому такая ошибка никогда не приведет к сбою. Он просто будет использовать более медленную копию. Кроме того, такая ошибка возникает и для неконстантных объектов, у которых нет конструкторов перемещения, поэтому простое добавление constперегрузки не перехватит их все. Мы могли бы проверить возможность перемещения конструкции или перемещения присваивания из типа параметра, но это будет мешать общему коду шаблона, который, как предполагается, использует конструктор копирования. И, черт возьми, может быть, кто-то хочет иметь возможность строить, из const CAT&&кого я такой, чтобы сказать, что он не может?

Мычание утки
источник
Превосходно. Стоит отметить неявное удаление copy-ctor после пользовательского определения обычного конструктора перемещения или оператора присваивания, который также продемонстрирует это через прерванную компиляцию. Хороший ответ.
WhozCraig
Также стоит упомянуть, что конструктор копирования, которому требуется значение, отличное от constl, также не поможет. [class.copy] §8: «В противном случае неявно объявленный конструктор копии будет иметь форму X::X(X&)»
Deduplicator
9
Я не думаю, что он имел в виду «ловушку» в терминах компьютера / сборки. Я думаю, он имел в виду, что это «ловушка» его подлый подлый, потому что, если он не осознает, он заканчивает с копией, которая не то, что он намеревался. Я думаю ..
Брэндон
Можете ли вы получить еще 1 лайк, а я - еще 4 за золотой значок. ;)
Yakk - Адам Неврамонт
Дайте пять на этом образце кода. Очень хорошо понимает суть.
Брент пишет код
20

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

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

Круто, теперь я могу относительно эффективно создать a vector<string>из a, deque<string>и каждый человек stringбудет перемещен в процессе.

Но что, если я захочу переехать из а map?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

Если std::moveнастаивать на отсутствии constаргумента, приведенный выше экземпляр move_eachне будет компилироваться, потому что он пытается переместить const int( key_typeиз map). Но этому коду все равно, если он не может переместить key_type. Он хочет переместить mapped_type( std::string) по соображениям производительности.

Именно для этого примера и бесчисленных других примеров, подобных этому в общем кодировании, std::moveявляется запрос на перемещение , а не требование на перемещение.

Говард Хиннант
источник
2

Меня беспокоит то же, что и ОП.

std :: move не перемещает объект и не гарантирует подвижность объекта. Тогда почему это называется ходом?

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

1. Тип движения - const.

Причина, по которой у нас есть ключевое слово const в языке, заключается в том, что мы хотим, чтобы компилятор предотвращал любые изменения объекта, определенного как const. Рассмотрим пример из книги Скотта Мейерса:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     {  } // doesn't do what it seems to!    
     
    private:
     std::string value;
    };

Что это буквально означает? Переместите константную строку в член значения - по крайней мере, это я понял, прежде чем читать объяснение.

Если язык намеревается не выполнять перемещение или не гарантирует, что перемещение применимо при вызове std :: move (), то при использовании слова move это буквально вводит в заблуждение.

Если язык поощряет людей, использующих std :: move, повысить эффективность, он должен предотвращать подобные ловушки как можно раньше, особенно для этого типа очевидного буквального противоречия.

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

2. У объекта нет конструктора перемещения.

Лично я думаю, что это отдельная история от беспокойства OP, как сказал Крис Дрю.

@hvd Мне кажется, это не аргумент. Тот факт, что предложение OP не исправляет все ошибки в мире, не обязательно означает, что это плохая идея (возможно, это так, но не по той причине, которую вы указываете). - Крис Дрю

Bogeegee
источник
1

Я удивлен, что никто не упомянул об этом аспекте обратной совместимости. Я считаю, что он std::moveбыл специально разработан для этого в C ++ 11. Представьте, что вы работаете с устаревшей кодовой базой, которая в значительной степени полагается на библиотеки C ++ 98, поэтому без отката при назначении копии перемещение может сломать ситуацию.

мл
источник