
Rvalue reference
&&- rvalue reference (&- lvalue reference)- позволяет передавать в функцию
rvalue - продлевает жизнь временным объектам (так же как и обычная)
- move constructor
- move assignment operator (
operator=(&&)) - reference collapsing
пример
int&& func(int&& i) {
// тут типо работаем с i как с lvalue reference
return i; // а тут типо возвращаем копию, которая rvalue
}
int main() {
int&& i = 1;
const int&& j = 2;
std::cout << func(1);
int x = 2;
int&& rx = x; // error
const int&& crx = x; // error
return 0;
}
- по переменной
хконкретно понимаем в каком месте памяти лежит переменная. Есть идентика rvalue-ссылка позволяет передатьrvalueв выражение, но сам по себе аргумент переданный какrvalue-ссылка. Но в тот момент, когда мы передалиrvalueпоrvalue-ссылке сталlvalue.- Если можно передать по обычной ссылке, то передаётся по простой ссылке (неконстантный в неконстанту)
rvalueпередаётся поrvalue, если естьrvalue-перегрузка. Если нет, то по константной ссылке.
move-constructor and move assigment
- принимают на вход
rvalue-reference - знаем, что он нам больше не нужен
- например, для массива можно просто передать указатели на начало.
- Экономим на пересоздании объекта после знания о том, что он нам больше не понадобится
- передают все значения полей в текущий объект
- Оставляет копируемы объект в инвариантном но неопределённом состоянии
- очищают ресурсы текущего объекта
default/delete- Правило 5
- Правило 0 (если всё по умолчанию удовлетворяют. Для
move- это чистоswap-ы)
int main() {
CArray arr1{5};
CArray arr2{};
arr2 = arr1; // lvalue
arr2 = createArray(); // prvalue
arr2 = std::move(arr1); // xvalue
return 0;
}
std::moveделает изlvalue-xvalue, при передаче в функцию будетrvalue- просто кастит статиком к
rvalue-ссылке
template <class _Tp>
typename remove_reference<_Tp>::type&&
move(_Tp&& __t) _NOEXCEPT {
typedef typename remove_reference<_Tp>::type _Up;
return static_cast<_Up&&>(__t);
}
lvalue-переменная послеmove-копирования(std::move) всё ещё жива и валидна. но в неопределённом состоянии -> её можно дальше использоватьв операторе присваивания до этого делали копию для безопасности
в
assignоператоре теперь простоSwap- (
copy-and-swap idiom) однако оператор присваивания иassign-оператор очень похожи, поэтому можно сделать один оператор присваивания, в который будем передавать объект по копии - если готовы пожертвовать тем, что при копировании в функцию будет вызван
move-constructor, то можем делать один и тот же оператор. Иначе - пишем 2 разных (кажется, что неверно написано объяснение… В примере было вызвано 2 дефолтных конструктора)
- (
Эффективный swap-трижды сделать move вместо копирований
template<typename T>
void std::swap(T& x, T& y)
T tmp = move(x);
x = move(y);
y = move(tmp);
}
Forwarding reference
template<typename T>
void function(T&& value) {
}
int main(int, char**) {
Foo** foo = Foo{};
auto && value = foo;
}
универсальная ссылка
Если у нас шаблон
ТиT&&, то это неrvalue, аforwardingпойдёт по
lvalue, еслиТ-lvalueпойдёт по
rvalue, еслиT-rvalueВ момент компиляции понимает на что ему заменить Т (дописать)
Foo& &->Foo&Foo&& &->Foo&Foo& &&->Foo&Foo&& &&->Foo&&Если создаём что-то с универсальной ссылкой, то оно становится
lvalueобъектом и нам дальше придётся кидать какlvalue&, но нам не всегда хочется это делать. (move сделать тоже не можем, потому чтоlvalueобъект мы не хотим мувать)- помогает
std::forward lvalueскастит кlvaluervalueскастит кrvalue- в отличии от
std::move, который делает это безусловно
- помогает
в
moveиforward static-castиспользуется по делу, поэтому можно не переживать (+делается на этапе компиляции)
copy elision
механика, при которой существует избавление от лишних копирований. (см пример со слайда
К примеру если возвращается что-то, чем мы потом будем инициализировать переменную, то можно сразу передать адрес объект к тому, что мы будем инициализировать, тогда не будет лишних конструкторов) (rvo)
даже если мы не сразу передаём по копии, а сначала конструируем в теле функции, то также происходит оптимизация(nrvo)
там, где неоднозначно(к примеру тернарный оператор при возвращении), то адрес объекта подставить нет возможности и оптимизации не будет
std::move- снимает с типа какую-либо ссылку или копию, то возвращаетrvaluestd::forward- оставляет тот же самый тип