- Что это такое?
- Что он делает?
- Когда он должен использоваться?
Хорошие ссылки приветствуются.
Хорошие ссылки приветствуются.
Страница Википедии на C++ 11 Ссылка на R-значение и конструкторы перемещения
Type &&
).std::move()
- это приведение, которое создает rvalue-ссылку на объект, чтобы разрешить перемещение с него. Это новый C++ способ избежать копий. Например, используя конструктор перемещения, std::vector
может просто скопировать свой внутренний указатель на данные в новый объект, оставив перемещенный объект в неправильном состоянии, избегая копирования всех данных. Это будет C++ -действительным.
Попробуйте прибегнуть к поиску семантики перемещения, значения, идеальной пересылки.
Хотя std::move()
технически является функцией - я бы сказал, что на самом деле это не функция. Это своего рода конвертер между способами, которыми компилятор считает значение выражения.
Первое, что нужно отметить, это то, что std::move()
фактически ничего не перемещает. Он преобразует выражение из lvalue (например, именованной переменной) в xvalue. Значение x сообщает компилятору:
Вы можете ограбить меня, переместить все, что у меня есть, и использовать это где-нибудь еще (поскольку я все равно скоро буду уничтожен) ".
другими словами, когда вы используете std::move(x)
, вы позволяете компилятору каннибализировать x
. Таким образом, если x
имеет, скажем, свой собственный буфер в памяти - после std::move()
вместо этого у компилятора может быть другой объект, которому он принадлежит.
Вы также можете перейти от значения (например, временного, который вы проходите), но это редко полезно.
Другой способ задать этот вопрос: "Для чего я мог бы уничтожить ресурсы существующего объекта?" хорошо, если вы пишете код приложения, вы, вероятно, не будете много возиться с временными объектами, созданными компилятором. Так что в основном вы бы делали это в таких местах, как конструкторы, операторные методы, функции, аналогичные стандартным библиотечным алгоритмам и т.д., Где объекты создавались и уничтожались автоматически много. Конечно, это просто правило.
Типичное использование - это "перемещение" ресурсов с одного объекта на другой вместо копирования. @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 указателя на буферы векторов плюс размеры 3 буферов. Конечно, класс T
должен знать, как сделать перемещение; Ваш класс должен иметь оператор присваивания перемещения и конструктор перемещения для класса T
, чтобы это работало.
Вы можете использовать перемещение, когда вам нужно "перенести" содержимое объекта куда-то еще, не делая копию (то есть содержимое не дублируется, поэтому его можно использовать с некоторыми не копируемыми объектами, такими как unique_ptr). Кроме того, объект может получить содержимое временного объекта без копирования (и сэкономить много времени) с помощью std::move.
Эта ссылка действительно помогла мне:
http://thbecker.net/articles/rvalue_references/section_01.html
Извините, если мой ответ приходит слишком поздно, но я также искал хорошую ссылку для std::move, и я нашел ссылки выше немного "строгими".
Это делает упор на ссылку r-value, в каком контексте вы должны их использовать, и я думаю, что это более подробно, поэтому я хотел поделиться этой ссылкой здесь.
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? Это, скорее всего, произойдет в шаблонных или библиотечных функциях, где вы знаете, что промежуточный результат может быть спасен.
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. Остальное использует семантику перемещения. Оператор перемещения или оператор присваивания также необходимо реализовать, чтобы присвоить результат А.
"Что это?" и "Что это делает?" было объяснено выше.
Я приведу пример "когда это следует использовать".
Например, у нас есть класс с большим количеством ресурсов, таких как большой массив.
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
также может быть полезен при сортировке массива элементов. Многие алгоритмы сортировки (такие как выборочная сортировка и пузырьковая сортировка) работают путем обмена парами элементов. Ранее нам приходилось прибегать к семантике копирования, чтобы выполнить обмен. Теперь мы можем использовать семантику перемещения, которая более эффективна.
Это также может быть полезно, если мы хотим переместить содержимое, управляемое одним умным указателем, в другое.
Цитируется: