Что такое std :: move () и когда его следует использовать?

430
  • Что это такое?
  • Что он делает?
  • Когда он должен использоваться?

Хорошие ссылки приветствуются.

  • 31
    Бьерн Страуструп объясняет ход в кратком введении в Rvalue Ссылки
  • 1
    Переместить семантику
Показать ещё 1 комментарий
Теги:
c++11
move-semantics

6 ответов

202
Лучший ответ

http://en.wikipedia.org/wiki/C%2B%2B11#Rvalue_references_and_move_constructors
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html#Move_Semantics

  • В С++ 11, помимо конструкторов копирования, объекты могут иметь конструкторы перемещения.
    (И в дополнение к операциям присваивания копий они имеют операторы присваивания перемещения.)
  • Конструктор перемещения используется вместо конструктора копирования, если у объекта есть тип "rvalue-reference" (Type &&).
  • std::move() - это трансляция, которая создает ссылку rvalue для объекта, чтобы перейти от него.

Это новый С++ способ избежать копирования. Например, используя конструктор перемещения, std::vector может просто скопировать свой внутренний указатель на данные в новый объект, оставив перемещенный объект в неправильном состоянии, избегая копирования всех данных. Это будет С++ - действительный.

Попробуйте выполнить поиск в googling для семантики перемещения, rvalue, совершенной пересылки.

  • 28
    Семантика перемещения требует, чтобы перемещенный объект оставался действительным , что не является неправильным состоянием. (Обоснование: он все еще должен разрушить, заставить его работать.)
  • 11
    @GMan: ну, он должен быть в состоянии, которое можно разрушить, но, AFAIK, его не нужно использовать ни для чего другого.
Показать ещё 6 комментариев
121

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

Эта ссылка действительно помогла мне:

http://thbecker.net/articles/rvalue_references/section_01.html

Извините, если мой ответ придет слишком поздно, но я также искал хорошую ссылку для std :: move, и я нашел ссылки выше немного "строгие".

Это делает акцент на ссылке r-value, в которой вы должны использовать их, и я думаю, что это более подробно, поэтому я хотел бы поделиться этой ссылкой здесь.

  • 19
    Хорошая ссылка. Я всегда находил статью в Википедии и другие ссылки, которые наткнулся на меня, довольно запутанными, поскольку они просто бросают в вас факты, предоставляя вам возможность выяснить, каково действительное значение / обоснование. Хотя «семантика перемещения» в конструкторе довольно очевидна, все эти подробности о передаче && - значений не являются ... так что описание в стиле учебника было очень хорошим.
95

1. "Что это?"

Хотя std::move() технически является функцией, я бы сказал, что это не функция. Это своего рода конвертер между способами, которые компилятор считает значением выражения.

2. "Что он делает?"

Первое, что нужно отметить, это то, что std::move() фактически ничего не движет.

Если вы когда-либо смотрели анимационный сериал Bleach - это эквивалентно смягчению Quincy Seele Schneider Reishi.

Серьезно, однако, он преобразует выражение из значения lvalue или pure rvalue (такого как переменная, которую вы могли бы использовать в течение длительного времени, или временное, которое вы проходили некоторое время, соответственно), чтобы быть значением xvalue. Xvalue сообщает компилятору:

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

другими словами, когда вы используете std::move(x), вы разрешаете компилятору cannibalize x. Таким образом, если x имеет, скажем, собственный буфер в памяти - после std::move() компилятор может иметь другой объект, а не собственный.

3. "Когда его следует использовать?"

Другой способ задать этот вопрос: "Что я буду использовать /cannibalize для ресурсов объекта?" хорошо, если вы пишете код приложения, вы, вероятно, не будете много возиться с временными объектами, созданными компилятором. Таким образом, в основном вы делаете это в таких местах, как конструкторы, методы операторов, похожие на STL функции и т.д., Где объекты создаются и уничтожаются автоматически. Конечно, это просто эмпирическое правило.

Типичное использование - "перемещение" ресурсов от одного объекта к другому вместо копирования. @Guillaume ссылки на эту страницу, которая имеет простой короткий пример: замена двух объектов с меньшим количеством копий.

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we now have two copies of a
    a = b;      // we now have two copies of b (+ discarded a copy of a)
    b = tmp;    // we now have two copies of tmp (+ discarded a copy of b)
}

использование move позволяет вам обменивать ресурсы, а не копировать их:

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

Подумайте, что произойдет, когда T, скажем, vector<int> размера n. В первой версии вы читаете и записываете 3 * n элементов, во второй версии вы в основном читаете и записываете только 3 указателя на буферы векторов. Конечно, класс T должен знать, как сделать движение; у вас должен быть оператор move-assign и конструктор move для класса T для этого.

  • 0
    Долгое время я слышал об этой семантике перемещения, я никогда не изучал их. Из приведенного вами описания кажется, что это просто мелкая копия вместо глубокой копии.
  • 4
    @TitoneMaurice: за исключением того, что это не копия - оригинальное значение больше не может быть использовано.
Показать ещё 10 комментариев
26

Q: Что такое std::move?

A: std::move() - это функция из стандартной библиотеки C++ для кастинга на ссылку rvalue.

Симметрично std::move(t) эквивалентно:

static_cast<T&&>(t);

Rvalue является временным, которое не сохраняется за пределами выражения, которое его определяет, например, результат промежуточной функции, который никогда не сохраняется в переменной.

int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated

Реализация для std :: move() приведена в N2027: "Краткое введение в ссылки на Rvalue" следующим образом:

template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
    return a;
}

Как вы можете видеть, std::move возвращает T&& независимо от того, T&& ли оно значением (T), ссылочным типом (T&) или ссылкой rvalue (T&&).

В: Что он делает?

A: В качестве исполнителя он ничего не делает во время выполнения. Во время компиляции актуально только сказать компилятору, что вы хотите продолжить рассмотрение ссылки как rvalue.

foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)

int a = 3 * 5;
foo(a);     // how to tell the compiler to treat 'a' as an rvalue?
foo(std::move(a)); // will call 'foo(int&& a)' rather than 'foo(int a)' or 'foo(int& a)'

Что это не делает:

  • Сделайте копию аргумента
  • Вызовите конструктор копирования
  • Изменение объекта аргумента

В: Когда его следует использовать?

A: Вы должны использовать std::move если хотите вызвать функции, которые поддерживают семантику переноса с аргументом, который не является rvalue (временное выражение).

Это вызывает следующие вопросы для меня:

  • Что такое семантика перемещения? Перенос семантики в отличие от семантики копирования - это метод программирования, в котором элементы объекта инициализируются "взятием" вместо копирования других членов объекта. Такой "захват" имеет смысл только с указателями и ручками ресурсов, которые могут быть дешево переданы путем копирования указателя или целочисленного дескриптора, а не базовых данных.

  • Какие классы и объекты поддерживают перемещение семантики? Это зависит от вас, как разработчика, для реализации семантики перемещения в ваших собственных классах, если они выиграют от передачи своих членов вместо их копирования. После того, как вы реализуете семантику перемещения, вы получите непосредственную выгоду от работы многих программистов библиотеки, которые добавили поддержку для эффективного управления классами с семантикой перемещения.

  • Почему компилятор не может понять это самостоятельно? Компилятор не может просто вызвать другую перегрузку функции, если вы этого не сделаете. Вы должны помочь компилятору выбрать, следует ли вызывать регулярную или перемещаемую версию функции.

  • В каких ситуациях я хочу сказать компилятору, что он должен обрабатывать переменную как rvalue? Это, скорее всего, произойдет в шаблонных или библиотечных функциях, где вы знаете, что промежуточный результат может быть спасен.

  • 0
    Большой +1 для примеров кода с семантикой в комментариях. Другие топовые ответы определяют std :: move с использованием самого «move» - ничего толком не разъясняется! --- Я считаю, что стоит упомянуть, что отсутствие копирования аргумента означает, что исходное значение не может быть надежно использовано.
25

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

Итак, std:: move используется как предшественник семантики перемещения. Перемещение семантики - это, по сути, эффективный способ обращения с временными объектами.

Рассмотрим объект A = B + C + D + E + F;

Это красивый код, но E + F создает временный объект. Затем D + temp создает другой временный объект и так далее. В каждом нормальном "+" операторе класса возникают глубокие копии.

Например

Object Object::operator+ (const Object& rhs) {
    Object temp (*this);
    // logic for adding
    return temp;
}

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

Мы можем использовать семантику перемещения, чтобы "разграбить" временные объекты и сделать что-то вроде

 Object& Object::operator+ (Object&& rhs) {
     // logic to modify rhs directly
     return rhs;
 }

Это позволяет избежать ненужных глубоких копий. Что касается примера, единственной частью, где происходит глубокое копирование, теперь является E + F. Остальное использует семантику перемещения. Оператор перемещения или оператор присваивания также необходимо реализовать, чтобы присвоить результат А.

  • 3
    Вы говорили о семантике перемещения. Вы должны добавить в свой ответ, как можно использовать std :: move, потому что вопрос об этом задается.
  • 2
    @Koushik std :: move мало что делает, но используется для реализации семантики перемещения. Если вы не знаете о std :: move, вы, вероятно, тоже не знаете семантику перемещения
Показать ещё 2 комментария
0

What is it? и What does it do? было объяснено выше.

Я приведу пример того, when it should be used.

Например, у нас есть класс с большим количеством ресурсов, таких как большой массив.

class ResHeavy{ //  ResHeavy means heavy resource
    public:
        ResHeavy(int len=10):_upInt(new int[len]),_len(len){
            cout<<"default ctor"<<endl;
        }

        ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
            cout<<"copy ctor"<<endl;
        }

        ResHeavy& operator=(const ResHeavy& rhs){
            _upInt.reset(new int[rhs._len]);
            _len = rhs._len;
            cout<<"operator= ctor"<<endl;
        }

        ResHeavy(ResHeavy&& rhs){
            _upInt = std::move(rhs._upInt);
            _len = rhs._len;
            rhs._len = 0;
            cout<<"move ctor"<<endl;
        }

    // check array valid
    bool is_up_valid(){
        return _upInt != nullptr;
    }

    private:
        std::unique_ptr<int[]> _upInt; // heavy array resource
        int _len; // length of int array
};

Тестовый код:

void test_std_move2(){
    ResHeavy rh; // only one int[]
    // operator rh

    // after some operator of rh, it becomes no-use
    // transform it to other object
    ResHeavy rh2 = std::move(rh); // rh becomes invalid

    // show rh, rh2 it valid
    if(rh.is_up_valid())
        cout<<"rh valid"<<endl;
    else
        cout<<"rh invalid"<<endl;

    if(rh2.is_up_valid())
        cout<<"rh2 valid"<<endl;
    else
        cout<<"rh2 invalid"<<endl;

    // new ResHeavy object, created by copy ctor
    ResHeavy rh3(rh2);  // two copy of int[]

    if(rh3.is_up_valid())
        cout<<"rh3 valid"<<endl;
    else
        cout<<"rh3 invalid"<<endl;
}

как показано ниже:

default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid

Мы видим, что std::move with move constructor упрощает преобразование ресурсов.

Где еще std :: move полезно?

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

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

Цитируется:

https://www.learncpp.com/cpp-tutorial/15-4-stdmove/

Ещё вопросы

Сообщество Overcoder
Наверх
Меню