Что такое std :: move () и когда его следует использовать? - PullRequest
540 голосов
/ 05 августа 2010
  1. Что это?
  2. Что это делает?
  3. Когда это следует использовать?

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

Ответы [ 6 ]

228 голосов
/ 05 августа 2010

Страница Википедии на C ++ 11 Ссылка на R-значение и конструкторы перемещения

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

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

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

163 голосов
/ 19 ноября 2014

1.«Что это?»

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

2.«Что он делает?»

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

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

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

Вы также можете перейти от prvalue (например, к временному объекту, который вы передаете), но это редкополезный.

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

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

Типичное использование - это «перемещение» ресурсов с одного объекта на другой вместо копирования.@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)
}

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

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 должен знать, как сделать движение;Ваш класс должен иметь оператор присваивания перемещения и конструктор перемещения для класса T, чтобы это работало.

134 голосов
/ 22 июня 2012

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

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

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

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

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

48 голосов
/ 20 февраля 2017

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

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

Упрощенно std::move(t) эквивалентно:

static_cast<T&&>(t);

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

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 References» следующим образом:

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

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

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

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

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)`

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

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

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

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

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

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

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

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

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

28 голосов
/ 05 июня 2013

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. Остальная часть использует семантику перемещения. Конструктор перемещения или оператор присваивания также должны быть реализованы, чтобы присвоить результат A.

5 голосов
/ 03 августа 2018

«Что это?» и «Что это делает?» было объяснено выше.

Я приведу пример »когда это должно использоваться ".

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

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 с move constructor позволяет легко преобразовать ресурс.

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

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

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

Цитируется:

...