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

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

struct Cat {
   Cat(){}
};

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

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

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

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

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

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, мы могли бы легко найти ошибку, правильно?

Ответ 1

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 на a T const. Он возвращает a T const&&. У нас есть конструктор перемещения для strange, который принимает именно этот тип.

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

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

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

Ответ 2

Здесь есть трюк, который вы игнорируете, а именно, что std::move(cat) фактически ничего не движет. Он просто говорит компилятору попытаться двигаться. Однако, поскольку ваш класс не имеет конструктора, который принимает 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&&, кто я такой, чтобы сказать, что он не может?

Ответ 3

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

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

Прохладный, теперь я могу относительно эффективно создать vector<string> из 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 не скомпилировалось, потому что оно пыталось переместить a const int (key_type map). Но этот код не волнует, если он не может переместить key_type. Он хочет переместить mapped_type (std::string) по соображениям производительности.

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

Ответ 4

У меня такая же проблема, как и у OP.

std:: move не перемещает объект, не гарантирует, что объект перемещается. Тогда почему это называется 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;
    };

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

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

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

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

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

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

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