Что такое std::move() и когда его следует использовать?
Проблема: Необходимость в разъяснении функциональности и применения технологии
Что это?
Я столкнулся с [название технологии или инструмента], и мне не совсем ясно, что это такое и какие его основные характеристики.Что он делает?
Хотел бы получить ясное объяснение того, какие функции предоставляет данный инструмент и как он может быть использован в контексте разработки или решения конкретных задач.Когда его следует использовать?
Кроме того, мне необходимо понять, в каких случаях и для каких задач [название технологии или инструмента] является наилучшим выбором по сравнению с альтернативами.
Буду признателен за полезные ссылки на документацию или ресурсы для более детального изучения.
4 ответ(ов)
Вы можете использовать std::move
, когда вам нужно "перенести" содержимое объекта в другое место, не выполняя копирования (т.е. содержимое не дублируется, поэтому это может применяться к некоторым некопируемым объектам, таким как unique_ptr
). Также возможно, чтобы объект получил содержимое временного объекта без копирования (что экономит много времени), с помощью std::move
.
Эта ссылка действительно помогла мне:
http://thbecker.net/articles/rvalue_references/section_01.html
Извините, если мой ответ приходит слишком поздно, но я также искал хорошую ссылку по std::move
, и нашёл, что приведённые ссылки несколько "суровы".
Данная ссылка акцентирует внимание на ссылках на r-значения, в каком контексте их следует использовать, и я считаю, она более подробная, поэтому захотелось поделиться ей здесь.
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);
// логика для сложения
return temp;
}
Создание временного объекта в этой функции фактически бесполезно — эти временные объекты будут удалены в конце строки, когда они выйдут из области видимости.
Мы можем использовать семантику перемещения, чтобы "пограбить" временные объекты и сделать что-то вроде:
Object& Object::operator+ (Object&& rhs) {
// логика для изменения rhs напрямую
return rhs;
}
Это позволяет избежать ненужного глубокого копирования. В отношении примера единственное место, где происходит глубокое копирование, — это E + F
. Остальная часть использует семантику перемещения. Конструктор перемещения или оператор присваивания также необходимо реализовать для присваивания результата переменной A
.
"Что это?" и "Что это делает?" уже было объяснено выше.
Я приведу пример "Когда это следует использовать".
Предположим, у нас есть класс с большим массивом ресурсов в нем:
class ResHeavy { // ResHeavy означает тяжелый ресурс
public:
ResHeavy(int len=10):_upInt(new int[len]),_len(len){
cout << "конструктор по умолчанию" << endl;
}
ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
cout << "конструктор копирования" << endl;
}
ResHeavy& operator=(const ResHeavy& rhs){
_upInt.reset(new int[rhs._len]);
_len = rhs._len;
cout << "оператор присваивания" << endl;
return *this;
}
ResHeavy(ResHeavy&& rhs){
_upInt = std::move(rhs._upInt);
_len = rhs._len;
rhs._len = 0;
cout << "конструктор перемещения" << endl;
}
// Проверка на валидность массива
bool is_up_valid(){
return _upInt != nullptr;
}
private:
std::unique_ptr<int[]> _upInt; // тяжелый ресурс массива
int _len; // длина массива int
};
Исходный код тестирования:
void test_std_move2(){
ResHeavy rh; // только один int[]
// оператор rh
// после некоторых операций с rh он становится неиспользуемым
// преобразуем его в другой объект
ResHeavy rh2 = std::move(rh); // rh становится недействительным
// показываем rh, rh2, они валидны
if(rh.is_up_valid())
cout << "rh валиден" << endl;
else
cout << "rh недействителен" << endl;
if(rh2.is_up_valid())
cout << "rh2 валиден" << endl;
else
cout << "rh2 недействителен" << endl;
// новый объект ResHeavy, созданный с помощью конструктора копирования
ResHeavy rh3(rh2); // две копии int[]
if(rh3.is_up_valid())
cout << "rh3 валиден" << endl;
else
cout << "rh3 недействителен" << endl;
}
Вывод будет следующим:
конструктор по умолчанию
конструктор перемещения
rh недействителен
rh2 валиден
конструктор копирования
rh3 валиден
Мы видим, что std::move
с конструктором перемещения
упрощает трансформацию ресурсов.
Где еще полезен std::move
?
std::move
также может быть полезен при сортировке массива элементов. Многие алгоритмы сортировки (такие как сортировка выбором и сортировка пузырьком) работают путем обмена парами элементов. Ранее мы использовали семантику копирования для выполнения обменов. Теперь мы можем использовать семантику перемещения, что является более эффективным решением.
Это также может быть полезно, если мы хотим переместить содержимое, управляемое одним умным указателем, в другой.
Ссылки:
std::move
просто приводит переменную к rvalue-ссылке, которая обозначается с помощью &&
. Предположим, у вас есть класс Foo
, и вы создаете объект следующим образом:
Foo foo = Foo();
Если затем вы напишете:
Foo foo2 = std::move(foo);
это будет эквивалентно тому, если бы вы написали:
Foo foo2 = (Foo&&) foo;
std::move
заменяет это приведение к rvalue-ссылке. Причина, по которой вы можете захотеть написать любую из предыдущих двух строк кода, в том, что если вы напишете:
Foo foo2 = foo;
то будет вызван конструктор копирования. Предположим, что экземпляры Foo
содержат указатель на какие-то данные в кучи, которые они владеют. В деструкторе Foo
эти данные из кучи удаляются. Если вы хотите различать копирование данных из кучи и передачу права собственности на эти данные, вы можете написать конструктор, который принимает const Foo&
, и этот конструктор может выполнять глубокое копирование. Затем вы можете написать конструктор, который принимает rvalue-ссылку (Foo&&)
, и этот конструктор может просто перенастроить указатели.
Этот конструктор, который принимает Foo&&
, будет вызван, когда вы пишете:
Foo foo2 = std::move(foo);
и когда вы пишете:
Foo foo2 = (Foo&&) foo;
Что такое лямбда-выражение и когда его следует использовать?
Почему следует использовать указатель вместо самого объекта?
push_back против emplace_back: в чем разница?
Что означает T&& (двойной амперсанд) в C++11?
Какова разница между 'typedef' и 'using'?