проблема: при вызове конструктора S сначала создасться временный объект стринга, который потом инициализирует data. хотелось бы, чтобы временного объекта не создавалось

мувнуть - операция, забирающее у другого объекта владение, оставив его пустым, а себе присвоить данные мув конструктор
std::string(std::string&& other) : arr(other.arr), sz(other.sz), cap(other.cap) {
other.arr = nullptr; // зануляем, чтобы тот деструктор не сделал doublefree
other.sz = other.cap = 0; // объект остался пустым и он всё ещё валидный
}мув конструктор - это констуктор, который принимает T&&
мув оператор присваивания
std::string& operator=(std::string&& other) {
delete[] arr;
arr = other.arr; other.arr = nullptr;
sz = other.sz; other.sz = 0;
cap = other.cap; other.cap = 0;
return *this;
}rule of 5 - если реализован хоть один нетривиально, то нужно реализовать все
- copy constructor
- dtor
- copy assignment operator
- move ctor
- move assignment operator
дефолтный мув конструктор: вызывает мув конструкторы полей для примитивных типов мув ничем не отличается от копирования
если ничего не написать про move ctor или move=, то компилятор их не будет генерировать и вместо move будет всегда вызываться копирование
бывают типы, которые мувать можно, копировать нельзя - если своему типу определить мувающие операции, а копирующие нет или явно запретить - например std::unique_ptr
когда вызываются мувающие операции, а когда копирующие операции? при вызове от rvalue - мувающие, от lvalue - копирующие, но можно заставить компилятор вызвать мувающую операцию - для этого есть std::move - функция, позволяющая вызвать мувающие операции от lvalue

плохой свап, тк если Т - нетривиальный тип, то будет работать за трижды линейное время
template <typename T>
void swap(T& x, T& y) {
T tmp = x;
x = y;
y = tmp;
}хороший свап, работает за константу (если объекты можно мувать)
template <typename T>
void swap(T& x, T& y) {
T tmp = std::move(x);
x = std::move(y);
y = std::move(tmp);
}роль std::move - каст к rvalue - сделать явный каст к &&, поэтому в большинстве случаев будет эквивалентно вместо std::move писать static_cast<T&&> если вызвать std::move от объекта и никуда это не присвоить, то с объектом ничего не случится. std::move не дает никаких инструкций процессору, а влияет лишь на этапе компиляции - перенаправляет функции по другой версии перегрузки
rvalue ссылки
T&& - такая ссылка, которая ведёт себя как обычная ссылка, но если её вернуть из функции, то это будет считаться rvalue переменные типа T&& - всё равно lvalue
T&& - rvalue ссылка - это тип, который во всём аналогичен обычной ссылке, но имеет 2 отличия:
- главное свойство rvalue ссылки - будучи возвращённой из функции, то это будет rvalue expression
- rvalue ссылку можно проинициализировать только rvalue expression
эти 2 условия верны всегда, кроме случая, когда rvalue& от T является типом аргумента шаблонной функции от T если функция принимает тип вида T&&, где T - это шаблонный параметр данной функции, то такая ссылка работает по иным правилам, нежели обычная rvalue& и называется универсальной ссылкой (forwarding reference) именно T&&, не const T&&, не vector<T&&>
template <typename T>
struct s {
void f(T&&) {}; //нет, не универсальная
}
это сделано для того, чтобы таким синтаксисом можно было принимать как rvalue, так и lvalue
rvalue ссылки, также как и константные ссылки продлевают жизнь временному объекту
имя переменной - это всегда lvalue, неважно какой у неё тип
в каком то смысле над rvalue определено больше операций, чем над lvalue (rvalue можно мувать). и std::move - каст между value (каст к rvalue&&)
если вызвать std::move от константного типа, то будет копирование, а не мув
создастся шаблонная функция
const T&& move (const T& x) { static_cast<const T&&>(x)}
муванье примитивных типов тоже самое, что их копирование
последняя строка опустошит x, y и z, а t будет “abc”
мув скастит к const string&&
дальше стринг y создасться либо из const string&, либо от string&&
второй конструктор невозможен, тк там нарушена константность
значит выбирается первый и происходит копирование
const независимо от value защищает от изменения объекта
const квалификаторы - позволяют отличать вызов от обычного объекта или от константного
есть ещё ссылочные квалификаторы, которые позволяют различать вызов от lvalue или от rvalue
string getData() && {return std::move(str)} - доступен только от rvalue (принимает this по rvalue ссылке и может свои данные отдать кому-то)
string getData() & {return str} - доступен только от lvalue (принимает себя как неконстантную lvalue ссылку, которая не может быть проинициализирована rvalue)
но string getData() const & {return str} - можно вызывать и от lvalue и от rvalue (означает, что принимаем this по константной ссылке)
для конструкторов квалификаторы неприменимы
forwarding references, std::forward
универсальной ссылкой называется ссылка, которая является
- её тип - T&&
- T - шаблонный параметр функции (функции, не класса)
- является аргументом шаблонной функции.
может принимать как rvalue, так и lvalue и вывод типов для неё работает по другим правилам. если подать lvalue, то на T навесится &
главная проблема универсальных ссылок - либо мы принимаем ваще любой тип, либо это не универсальная ссылка
проблема: как пушбечить в вектор, если мы хотим мувнуть объект, но в векторе аллокатор вызывает конструктор в векторе от const T&, что создает копирование? конструктор от T&& не поможет, тк у объекта есть аргументы (const Args&… args), которым нужно сохранить тип value
на самом деле при передаче аргументов для конструктора стоит Args&&… args
правильная передача аргументов с сохранением вида value:
void construct(u* ptr, Args&&... args) {
new (ptr) U(std::forward<Args>(args)...)
}
многоточие разворачивает и делает U(std::forward<Args1>(arg1), std::forward<Args2>(arg2)) итд`
std::forward в зависимости от того, были ли объекты приняты как lvalue или rvalue, дальше передает их с сохранением типа value, только если объекты были приняты по rvalue&
T&& - универсальная ссылка, только если она - аргумент функции

perfect forwarding

создание вектора из переменного числа аргументов. прикольное применение оператора ,

если вызывать функцию от lvalue и принимая аргумент по универсальной ссылке, то тип будет T&, а если от rvalue, то T
referencing collapsing rules - сворачивание ссылок, происходит в инстанцировании шаблона и определения auto переменных
если в момент вывода типа в шаблоне накладываются &, то:
- & + & -> &
- && + & -> &
- & + && -> &
- && + && -> && костыль для универсальных ссылок, когда компилятор искусственно добавляет &
template <typename T>
T&& move(T& value) {
return static_cast<T&&>(value);// наивная реализация std::move
//на самом деле почти рабочая реализация std::forward
}если подать в эту функцию int&, то произойдёт reference collapsing - T& к T&& = T&, то есть вернется lvalue (T&) - мув никогда не должен возвращать lvalue, реализация не работает
правильная реализация std::move
template <typename T>
std::remove_reference_t<T>&& move(T&& value) {
return static_cast<std::remove_reference_t<T>&&>(value);
}мув принимает всегда универсальную ссылку, а возвращает rvalue ссылку
правильная реализация std::forward
template <typename T>
T&& forward(std::remove_reference_t<T>&(value) {
return static_cast<T&&>(value);
}её перегрузка
T&& forward(std::remove_reference_t<T>&&(value) {
static_assert(!std::is_lvalue_reference_v<T>);
return static_cast<T&&>(value);
}форвардит rvalue как rvalue и запрещает форвардить rvalue как lvalue редкий случай. вызывается тогда, когда вызывается std::forward от результата вызова функции от аргументов, которые иногда могут вернуть rvalue и ещё запретить, чтоб std::forward<T&>(5); компилировалось, тк это возвращает ссылку на временный объект - UB
в std::forward принимаемый тип - std::remove_reference_t<T>&, а не просто T&, потому что то компилятор неявно выведет тип T&& и мувнет lvalue, чего мы не хотим поэтому там стоит метафункция, которая помешает компилятору вывести тип неявно
std::forward нужен только для того, чтобы правильно обрабатывать параметры, переданные по универсальной ссылке (когда мы не знаем исходный вид value объекта) мы всегда можем использовать std::forward вместо std::move, однако выше шанс ошибиться, тк требуется явное указание шаблонного типа
пушбеки с мувсемантикой
или

мув конструктор должен быть noexcept если в своем классе не объявить move ctor noexcept, то при реалокации в векторе, он будет копировать все объекты, хотя мог бы мувать
если тип таков, что его нельзя безопасно мувать, но при этом нельзя копировать, то в векторе не будет гарантии безопасности а если его можно копировать, то все безопасно, но будет копироваться
с C++11 есть смысл отказаться от перегрузки функций по разным видам value ссылок (const T& и T&&). если все объекты заведомо поддерживают мувсемантику, то не нужно делать 2 перегрузки для lvalue и rvalue, можно просто принимать по значению. и тогда обязанность вызывающего решить копировать или мувать (просто передавать или передавать std::move или прост rvalue)
минус такого способа: всегда будет +1 дополнительное перемещение и если оно не тривиальное, то всё плохо
точная подстановка лучше, чем соответствие типа
не A, тк у нас a - не const
не B, тк у нас a - не rvalue
в C мы можем сделать такую шаблонную подстановку, что будет точное соответствие типов
подстановка - Optional<int>&
принимаемый тип будет Optional<int>&, это лучше, чем const Optional& (A), а B впринцепе не кандидат
а сейчас будет B, тк теперь B - частный случай C
std::exchange
template <typename T, typename U = T>
T exchange(T& obj, U&& new_value)
эффективно заменяет значение obj значением new_value и возвращает obj
мувает, когда можно мувнуть, иначе копирует

expired values
gvalue - generalized lvalue
xvalue - expired value
prvalue - pure rvalue
к xvalue относятся:
- результаты вызова функции, возвращаемый тип которых rvalue&&
- каст к rvalue&& (мув)
- обращение к полю rvalue
- [] от rvalue массива
- запятая, если её правая часть xvalue
- тернарный оператор, если одна из его частей xvalue
copy elision
std::string s = std::string("abc"); - здесь вызовется всего 1 констуктор (от const char*)
std::string s = std::move(std::string("abc")); - результат std::move - xvalue, значит оно должно где-то лежать в памяти. создаётся временная строка, а потом мувнется в s
если объект инициализируется посредством prvalue, то промежуточный объект можно не создавать
lvalue - это то, чему соответствует какая-либо ячейка памяти, а rvalue - это то, чему необязательно соответствует (фиругирует только в промежуточных вычислениях)
xvalue относится и к glvalue и к rvalue, тк ему соответствует ячейка памяти (должно быть создано и куда-то положено), но по свойствам как rvalue
компилятор имеет право не класть в память prvalue - промежуточные объекты с C++17 компиляторы обязаны не создавать промежуточные объекты из prvalue - guaranteed copy elision
temporary materialization (с C++17 из-за guaranteed copy elision) (материализация временного) - неявное преобразование из prvalue в xvalue, которое происходит в ограниченном наборе случаев
например - инициализация rvalue& через prvalue


lvalue-to-rvalue conversion
компилятор умеет не только неявно конвертировать prvalue к xvalue, он ещё умеет неявно конвертировать glvalue к prvalue неформально: чтение из памяти, например когда из под поинтера считали значение *p = *q *p lvalue *q - prvalue (типо из под q считали значение и положили в регистр)
при возврате из функции какого-то значения не надо писать std::move от этого, тк это убъёт RVO и заставит компилятор создать в памяти временный объект а при RVO будет ни copy, ни move RVO работает даже над lvalue, если это просто имя локальной переменной (Named RVO) (условные операторы убьют)
RVO применимо только для локальных переменных, для аргументов функции нельзя
здесь создасться лишь 1 объект S
copy elision
std::string str = std::string("abc");RVO
std::string getStrRVO() {
return std::string("abc");
}NRVO
std::string getStrNRVO() {
std::string s("abc");
return s;
}результат вызова функции, возвращающий rvalue& это xvalue возврат копии - prvalue
важный пример:
S f(S a) return a; - нету RVO
S t(0)
S s = f(t)
1 конструктор, 1 копиконструктор, 1 мув конструктор
почему мув конструктор при возврате из f? S выхода проинициализировалось “a” через муванье
правило: даже если компилятору не удалось применить RVO при возврате, но переменная локальная (хоть аргумент, хоть просто переменная, главное объект, не ссылка), то он её мувнет
когда return std::move() все таки нужно писать? когда приняли по rvalue& и возвращаете это же, что приняли если не писать, то копирование будет
0 копий, просто мув

как сломать std::move?
- константные объекты не муваются

из auto& может получится const S& и объект не мувнется

в константном векторе константные объекты (не мувнутся)

чтобы быстро проверить копирование

- auto по std::initializer_list -> const elems

здесь 3 мува, а потом 3 копирования
решение:
std::ref - reference wrapper, хранит внутри себя поинтер и .get() возврвщает ссылку на объект.
нужен именно он, тк initializer_list принимает по значению
если деструктор определён пользователем, то мув конструктор not declared (пропадёт и будет копироваться при муве)
можно задефолтить мув конструктор и тогда он будет