# Записи по всем лекциям по C++ Advanced > [TOC] # 01 Лекция. Передача параметров по значению, RVO, NRVO ## Передача параметров по значению При вызове функции, копирование в аргумент происходит снаружи, зделано это для оптимизации, при копировании снаружи - если мы не используем этот аргумент далее - его можно не копировать. ```cpp= struct test{}; void foo(test val) { // NOT here } int main() { test x(); // copy here foo(x); } ``` --- ## RVO & NRVO ```cpp= struct test {}; test foo() { test x(); return foo(); /// RVO return {}; /// RVO return x; /// NRVO } int main() { test x = foo(); } ``` Во всех этих случаях гарантированно нет копирования, начиная с С++17 эта оптимизация обязательна (но она и так была с 11) гарантируется в следующих случаях: * При return, когда операнд — prvalue того же типа, что возвращаемое значение функции. * При инициализации объекта, когда инициализатор — prvalue того же типа, что и переменная (без учёта const- и volatile-квалификаторов). Писать `std::move` в `return` не имеет смысла, тк он только подавит оптимизации. И даже если там не будет оптимизации - гарантированно компилятор туда сам подставит `std::move`! --- ## lifetime extension (C++03) При возврате переменных из функции их время жизни будет автоматически продлеваться, и не нужно копирования. ```cpp= #include <iostream> int foo() { int value = 42; return value; } int main() { int& x = foo(); // время жизни переменной value будет продленно std::cout << x << "\n"; } ``` https://en.cppreference.com/w/cpp/language/lifetime # 02 Лекция. rvalue-ссылки, move-семантика, xvalue, copy elision, lifetime extension ```cpp int a; /// value int &a; /// l-value reference int &&a; /// r-value reference ``` --- ```cpp int foo(); int& bar(); int&& baz(); int main() { decltype(foo()) x; /// prvalue - int x decltype(bar()) y; /// lvalue - int& y decltype(baz()) z; /// xvalue - int&& z } ``` --- ```cpp= int& a = 5; auto b = &a; /// int& ``` ```cpp & + & = & & + && = & && + & = & && + && = && ``` --- ## Values categories ![](https://i.imgur.com/P8YEJ6s.png) Термины lvalue и rvalue появились ещё в языке C. Исторически **lvalue** – это то, что может быть слева (left) от оператора присваивания, а **rvalue** – то, что может быть только справа (right). В C++11 основу новой более строгой терминологии легли 2 свойства: * наличие идентичности (identity) – т. е. какого-то параметра, по которому можно понять, ссылаются ли два выражения на одну и ту же сущность или нет (например, адрес в памяти); * возможность перемещения (can be moved from) – поддерживает семантику перемещения. Обладающие идентичностью выражения обобщены под термином glvalue (generalized values), перемещаемые выражения называются rvalue. Комбинации двух этих свойств определили 3 основные категории выражений: | | Обладают идентичностью | Лишены идентичности | | -------- | -------- | -------- | | Не могут быть перемещены | lvalue | - | | Могут быть перемещены | xvalue | prvalue | Источник: https://habr.com/ru/post/441742/ --- --- # 03 Лекция. intrusive контейнеры ![](https://i.imgur.com/GROBNgW.png) # 04 Лекция. shared_ptr 1) `weak_ptr lock` нужен для норм работой с многопоточностью (выдает `shared_ptr` на обьект вместо обычной ссылки) 2) `make_shared` нужен для оптимизации числа аллокаций (`control block` и обьект выделяются вместе) 3) `control block` жив до того момента, пока жив хоть один `weak_ptr` на обьект (и `shared+ptr` естественно) 4) `make_shared` + `weak_ptr` => память под обьект будет занята, пока жив `weak_ptr` (деструтор обьекта будет вызван правильно, когда уничтожиться последний `shared_ptr` на обьект) (смотри пункт 2-3) --- --- # 05 Лекция. perfect forwarding, universal ссылки, variadic шаблоны При Perfect Forwarding есть 2 свойства: 1) сохраняет модификаторы (const, ...) 2) сохраняет value categories (move, copy...) --- ```cpp template <typename T> void g(T x) /// T будет выводиться без const ``` --- `std::forward` можно полностью заменить на ```cpp static_cast<T&&>(x) ``` --- `std::remove_reference` внутри `std::forward` нужен только для того, чтобы подавить автоматическое вычисление типа (чтобы программисты не забывали писать тип при вызове `std::forward`) --- ```cpp template <typename T> void f(T&& x) {} ``` `T&& x` - универсальная ссылка, тк вычисляется и в l-ref и в r-ref --- ## variadic шаблоны ### множественное наследование ```cpp= template <typename... U> struct mytype : U... { using U::foo... }; ``` ### Как читать ... **3 варианта с std::forward** ```cpp= template <typename... Args> int g(Args&&... args) { return foo(std::forward<Args>(args)...); return foo( std::forward<Arg0>(arg0), std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), ...); //--------------------------------------------------------------------// return foo(std::forward<Args...>(args...)); return foo(std::forward<Arg0, Arg1, Arg2, ...>(arg0, arg1, arg2, ...)); //--------------------------------------------------------------------// return foo(std::forward<Args...>(args)...); return foo( std::forward<Arg0, Arg1, Arg2, ...>(arg0), std::forward<Arg0, Arg1, Arg2, ...>(arg1), std::forward<Arg0, Arg1, Arg2, ...>(arg2), ...); } ``` **4 примера с функциями** ```cpp= template <typename... Args> void zzz(Args... args) { f(g(args...)); f(g(arg0, arg1, arg2, ...)); //--------------------------------------------------------------------// f(g(args)...); f(g(arg0), g(arg1), g(arg2), ...)); //--------------------------------------------------------------------// f(g(args, args...)...); f( g(arg0, arg0, arg1, arg2, ...), g(arg1, arg0, arg1, arg2, ...), g(arg2, arg0, arg1, arg2, ...), ...); //--------------------------------------------------------------------// f(g(args, args)...); f( g(arg0, arg0), g(arg1, arg1), g(arg2, arg2), ...); } ``` ### 2 шаблонных пака в одной функции или структуре ```cpp= template <typename... Ts> struct tuple {}; template <typename... U, typename... V> struct tuple<tuple<U...>, tuple<V...>> {}; template <> struct tuple<> {}; int main() { tuple<tuple<int, int>, tuple<double, double>> t; } ``` # 06 Лекция. Возврат значения при perfect forwardingе, decltype, auto, nullptr ```cpp struct test {}; sizeof(test) == 1; ``` [**Источник**](https://www.geeksforgeeks.org/why-is-the-size-of-an-empty-class-not-zero-in-c/#:~:text=The%20size%20of%20an%20empty%20class%20is%20not%20zero.,objects%20will%20have%20different%20addresses.) --- ## decltype ```cpp int foo(); int& bar(); int&& baz(); int main() { decltype(foo()) x; /// prvalue - int x decltype(bar()) y; /// lvalue - int& y decltype(baz()) z; /// xvalue - int&& z } ``` --- ## 2 разных decltype Есть 2 разных `decltype`, для name и value ```cpp #include <type_traits> int main() { int x; // x is the name of an entity of type int static_assert(std::is_same_v<decltype(x), int>); // (x) is an expression of type int & static_assert(std::is_same_v<decltype((x)), int &>); } ``` --- `decltype(auto)` - https://ru.stackoverflow.com/questions/563318/%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D1%8F-decltypeauto --- ## Приколы с возвращаемым типом Что делать если у функции разные возращаемые значения при разных перегрузках, а вернуть результат хочется: ```cpp= char f(int&); std::string f(std::string); double* f(std::vector<short> const&); template <template T> T&& decval() noexept; template <typename T> dectype(f(declval<T>())) g(T&& x) { return f(std::forward<T>(x)); } ``` `declval<T>()` нужно только для вычисления типа x до его обьявления Но теперь можно написать нормально (после обьявления функции) ```cpp= template <typename T> auto g(T&& x) -> dectype(f(std::forward<T>(x))) { return f(std::forward<T>(x)); } ``` Ещё пример со `->` ```cpp= struct foobar { using type = int; type f(); void g(type); } auto foobar::f() -> type {} auto foobar::g(type x) {} ``` --- perfect_backward, сохраняющий RVO и NRVO ```cpp= template<int N> decltype(auto) perfect_backward() { decltype(auto) result = giver(index_constant<N>{}); printf("%d\n", result); if constexpr (std::is_reference_v<decltype(result)>) { return decltype(result)(result); } else { return result; } } ``` [**Источник**](https://quuxplusone.github.io/blog/2018/09/25/perfect-backwarding/) --- --- # 07 Лекция. Cтатический и динамический полиморфизм, анонимные функции Cортировка с функтором быстрее, чем сортировка с указателем на функции (на 30-40%), если же брать уже отсортированные массивы - быстрее в 2.5-3 раза! ```cpp= bool int_less(int a, int b) /// функция { return a < b; } template <typename T> struct less /// функтор { bool operator <(T const& a, T const& b) { return a < b; } } ``` Вызов функцтора в 3-4 раза быстрее, чем функции по указателю. Вызов `less<int>` **компилятор может встроить** (`inline`), а вызов `int_less` - нет. --- Есть специальные ключи компилятора `-fprofile-generate` и `-fprofile-use`, которые создают и используют профайл для самых оптимальных настроек компиляции. --- Лябда функции для написания функции только в данной точке кода (для удобства чтения) --- `[x, &y, z](){}` - x и z по значению, а y по ссылке `[=, &y]` - все по значению, а y по ссылке (захватывает this по ссылке) `[&, x, z]` - все по ссылке, а x и z по значению (захватывает this по ссылке) `[]` - ничего `[this]` - все по указателю this `[*this]` - все по копии текущего класса `[str = std::move(str)]` - строка помуванная ![](https://i.imgur.com/BOEVGDr.png) --- # 08 Лекция. Возращаемое значение у лябды ```cpp []() -> bool { } ``` --- Неконстантная лябда ```cpp struct mytype { void foo() { int n = 0; [n]() mutable { /// non-const return ++n; } } } ``` --- Шаблонная лямбда (C++20) ```cpp struct mytype { void foo() { int n = 0; []<typename U, typename V>(U const& a, V const& b) { return a < b; } } } ``` --- Написание Шаблонной лямбды (C++17) ```cpp struct mytype { void foo() { int n = 0; [](auto const& a, auto const& b) { /// (auto a, dectype(a) b) return a < b; } } } ``` --- Новое написание Шаблонной функции (C++20) ```cpp void foo(auto x) {} int main() { foo<int>(5); } ``` --- Ещё пример совмещения (`auto` должно идти последним(и)) ```cpp template <typename U> void foo(U a, auto x) {} int main() { foo<int, float>(5, 5.f); } ``` --- --- # 09 Лекция. # 10 Лекция. --- --- > [TOC]