
2 семестр
Основы программирования.
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
скастит кlvalue
rvalue
скастит кrvalue
- в отличии от
std::move
, который делает это безусловно
- помогает
в
move
иforward static-cast
используется по делу, поэтому можно не переживать (+делается на этапе компиляции)
copy elision
механика, при которой существует избавление от лишних копирований. (см пример со слайда
К примеру если возвращается что-то, чем мы потом будем инициализировать переменную, то можно сразу передать адрес объект к тому, что мы будем инициализировать, тогда не будет лишних конструкторов) (rvo)
даже если мы не сразу передаём по копии, а сначала конструируем в теле функции, то также происходит оптимизация(nrvo)
там, где неоднозначно(к примеру тернарный оператор при возвращении), то адрес объекта подставить нет возможности и оптимизации не будет
std::move
- снимает с типа какую-либо ссылку или копию, то возвращаетrvalue
std::forward
- оставляет тот же самый тип