├── .gitignore ├── cpp ├── img │ └── mem1.jpg ├── exceptions.md ├── idioms.md ├── ub.md ├── sfinae.md ├── additional.md ├── new_delete.md ├── type_traits.md ├── allocators.md ├── inheritance.md ├── move_semantics.md ├── templates.md └── miscellaneous.md ├── general ├── img │ └── protocols_stack.jpg ├── topics.md ├── additional.md ├── miscellanious.md ├── highload.md └── protocols.md ├── langs ├── py_add.md └── go_add.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /cpp/img/mem1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dasfex/notes/HEAD/cpp/img/mem1.jpg -------------------------------------------------------------------------------- /general/img/protocols_stack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dasfex/notes/HEAD/general/img/protocols_stack.jpg -------------------------------------------------------------------------------- /langs/py_add.md: -------------------------------------------------------------------------------- 1 | # Additional Python 2 | 3 | ### talks 4 | 1. [how Timsort works](https://youtu.be/Ye5pzBHB584?t=1260). 5 | 6 | ### stackoverflow questions 7 | 1. [hidden Python features](https://stackoverflow.com/questions/101268/hidden-features-of-python). 8 | 9 | ### Дополнительные источники 10 | 1. Курсы на степике: 11 | [для начинающих](https://stepik.org/course/67/syllabus) и 12 | чтобы представлять, [как юзать](https://stepik.org/course/512/syllabus), 13 | а ещё по [numpy](https://stepik.org/course/3356/syllabus). 14 | -------------------------------------------------------------------------------- /general/topics.md: -------------------------------------------------------------------------------- 1 | # Topics 2 | 3 | Статьи, которые по каким-то причинам (возможно пока) не были опубликованы в @thisnotes (искать в telegram). 4 | 5 | 1. [Not All Developers Want to Be Managers, and That’s OK]( 6 | https://blog.petrzemek.net/2019/09/02/not-all-developers-want-to-be-managers-and-thats-ok/). 7 | 2. [Чтобы найти хороших разработчиков, заставьте их читать чужой код](https://habr.com/ru/post/664678/). 8 | 3. [Пять типов вопросов на собеседованиях, которые я терпеть не могу](https://habr.com/ru/company/productivity_inside/blog/675798/). 9 | -------------------------------------------------------------------------------- /cpp/exceptions.md: -------------------------------------------------------------------------------- 1 | # Исключения 2 | 3 | 1. [Обработка ошибок и C++](https://habr.com/ru/post/690038/). 4 | 2. [С++ exception handling под капотом или как же работают исключения в C++](https://habr.com/ru/post/279111/). 5 | 3. [Что же там такого тяжелого в обработке исключений C++?](https://habr.com/ru/post/208006/). 6 | 4. [Разрушительные исключения](https://habr.com/ru/post/433944/). 7 | 5. [Исключения C++ через призму компиляторных оптимизация](https://habr.com/ru/company/jugru/blog/494986/). 8 | 6. C++ Russia 2021. Евгений Ерохин. [Exception Handling: богатый мир обработки исключений](https://www.youtube.com/watch?v=zBskaBF-Wxo). 9 | -------------------------------------------------------------------------------- /langs/go_add.md: -------------------------------------------------------------------------------- 1 | # Additional Golang 2 | 3 | ### talks 4 | 1. Rob Pike. [Concurrency is not Parallelism](https://www.youtube.com/watch?v=oV9rvDllKEg). 5 | 2. Golang Conf 2019. Егор Гришечко (Insolar). 6 | [Go Channels Internals]( 7 | https://www.youtube.com/watch?v=Tp5xhTMFuLU). 8 | 3. Golang Conf 2019. Григорий Петров (Evrone). 9 | [Как и зачем писать читаемый код]( 10 | https://www.youtube.com/watch?v=SS9ddV622Jk). 11 | 4. Highload++ Foundation 2022. Егор Гришечко (Uber). 12 | [Go Map Internals]( 13 | https://www.youtube.com/watch?v=3n1QkOI-y2g). 14 | 15 | ### Дополнительные источники 16 | 1. [Официальный курс](https://go-tour-ru-ru.appspot.com/welcome/1) в язык. 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /cpp/idioms.md: -------------------------------------------------------------------------------- 1 | ## ADL 2 | 3 | ADL - Argument dependent lookup(ADL). 4 | 5 | Рассмотрим следующий код: 6 | ```cpp 7 | namespace A { 8 | struct S {}; 9 | 10 | void call(const S& x) {} 11 | } 12 | ... 13 | A::S x; 14 | call(x); 15 | ``` 16 | Удивительно, но этот код скомпилируется, несмотря на то, что мы явно не указали 17 | namespace функции ```call```. 18 | Это называется ```argument dependency lookup```. 19 | Компилятор смотрит на пространства имён аргументов и в них ищет функцию. 20 | 21 | Что же будет, если подходящих вариантов несколько? 22 | ```cpp 23 | namespace A { 24 | struct S1; 25 | } 26 | 27 | namespace B { 28 | struct S2 {}; 29 | void call(const A::S1& x, const S2& y) { 30 | cout << "1"; 31 | } 32 | } 33 | 34 | namespace A { 35 | struct S1 {}; 36 | void call(const S1& x, const B::S2& y) { 37 | cout << "2"; 38 | } 39 | } 40 | ... 41 | A::S1 x; 42 | B::S2 y; 43 | call(x, y); 44 | ``` 45 | Как вариант, вызовется та версия, которая раньше будет найдена(какой аргумент раньше объявлен). 46 | Однако получим ошибку компиляции об неоднозначности вызова: 47 | ``` 48 | error: call of overloaded ‘call(A::S1&, B::S2&)’ is ambiguous 49 | ``` 50 | Существуют также ситуации, когда ADL не работает: 51 | ```cpp 52 | void foo(); 53 | namespace N { 54 | struct X {}; 55 | void foo(X) {}; 56 | void qux(X) {}; 57 | } 58 | 59 | struct C { 60 | void foo() {} 61 | void bar () { 62 | foo(N::X{}); // будет обнаружена C::foo, которая не принимает аргументы 63 | } 64 | }; 65 | 66 | void bar() { 67 | extern void foo(); // redeclare ::foo 68 | foo(N::X{}); // ::foo не требует аргументов 69 | } 70 | 71 | int qux; 72 | 73 | void baz() { 74 | qux(N::X{}); // variable declaration disables ADL for "qux" 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProgrammingNotes 2 | Some notes from different programming fields. 3 | 4 | ## C++ 5 | 6 | 1. [new, delete](https://github.com/dasfex/notes/blob/master/cpp/new_delete.md). 7 | 2. [allocators](https://github.com/dasfex/notes/blob/master/cpp/allocators.md). 8 | 3. [inheritance notes](https://github.com/dasfex/notes/blob/master/cpp/inheritance.md). 9 | 4. [type deduction notes + templates notes + metaprogramming notes](https://github.com/dasfex/notes/blob/master/cpp/templates.md). 10 | 5. [type traits](https://github.com/dasfex/notes/blob/master/cpp/type_traits.md). 11 | 6. [sfinae](https://github.com/dasfex/notes/blob/master/cpp/sfinae.md). 12 | 7. [move-semantics](https://github.com/dasfex/notes/blob/master/cpp/move_semantics.md). 13 | 8. [undefined behaviour](https://github.com/dasfex/notes/blob/master/cpp/ub.md). 14 | 9. [some idioms](https://github.com/dasfex/notes/blob/master/cpp/idioms.md). 15 | 10. [exceptions topics](https://github.com/dasfex/notes/blob/master/cpp/exceptions.md). 16 | 10. [miscellaneous](https://github.com/dasfex/notes/blob/master/cpp/miscellaneous.md). 17 | 11. [additional](https://github.com/dasfex/notes/blob/master/cpp/additional.md): talks and more info. 18 | 19 | ## General 20 | 21 | 1. [protocols(udp/tcp)](https://github.com/dasfex/notes/blob/master/general/protocols.md). 22 | 2. [miscellanious](https://github.com/dasfex/ProgrammingNotes/blob/master/general/miscellanious.md). 23 | 3. [additional](https://github.com/dasfex/notes/blob/master/general/additional.md): more info and links. 24 | 25 | ## Additional for some fields 26 | 27 | 1. [python](https://github.com/dasfex/notes/blob/master/langs/py_add.md). 28 | 2. [golang](https://github.com/dasfex/notes/blob/master/langs/go_add.md). 29 | 3. [highload](https://github.com/dasfex/notes/blob/master/general/highload.md). 30 | -------------------------------------------------------------------------------- /general/additional.md: -------------------------------------------------------------------------------- 1 | # Additional general 2 | 3 | ##### Алгоритмы 4 | 5 | 1. Адитья Бхаргава, "Грокаем алгоритмы". Очень хорошая книга для самого-самого начала. 6 | 2. [e-maxx rus](http://e-maxx.ru/algo/). 7 | 3. [e-maxx eng](https://cp-algorithms.com)(активно дополняется и исправляется, чем русская версия похвастаться не может). 8 | 4. [algorithmica](https://algorithmica.org/ru/), 9 | [codeforces](https://codeforces.com), 10 | [acmp](https://acmp.ru/asp/do/index.asp?main=course&id_course=2), 11 | [dl.gsu.by](http://dl.gsu.by), 12 | [informatics.mccme](https://informatics.mccme.ru). 13 | 5. Визуализаторы: 14 | [1](https://www.cs.usfca.edu/~galles/visualization/Algorithms), 15 | [2](https://visualgo.net/en), 16 | [3](https://algorithm-visualizer.org) и 17 | для [алгоритмов кратчайших путей на графах на плоскости](http://qiao.github.io/PathFinding.js/visual/). 18 | 6. [leetcode.com](https://leetcode.com) - норм сервис для подготовки к собесам. 19 | 20 | ##### Git 21 | 22 | 0. Как это работает: [1](https://vas3k.ru/blog/319/), [2](https://vas3k.ru/blog/320/). 23 | 1. [ohshitgit](https://ohshitgit.com). 24 | 2. [--force](https://blog.developer.atlassian.com/force-with-lease/#:~:text=Git's%20push%20--force%20is,has%20pushed%20in%20the%20meantime.). 25 | 3. [Удачная модель ветвления](https://habr.com/ru/post/106912/). 26 | 27 | ##### Что-то околоменеджерское 28 | 29 | 1. Про адекватные собеседования: [раз](https://vas3k.ru/inside/46/), [два](https://habr.com/ru/post/512160/). 30 | 2. Зачем на них [ходить](https://habr.com/ru/company/ruvds/blog/521086/). 31 | 3. Какая-то рандомная [история](https://habr.com/ru/post/521104/). 32 | 33 | ##### Рандомное (todo) 34 | 35 | 0. [Викиконспекты](https://neerc.ifmo.ru/wiki/index.php?___BEби=#.D0.9D.D0.B5.D0.BF.D1.80.D0.BE.D0.B2.D0.B5.D1.80.D1.8F.D0.B5.D0.BC.D1.8B.D0.B5_.D0.BA.D0.BE.D0.BD.D1.81.D0.BF.D0.B5.D0.BA.D1.82.D1.8B) 36 | по всему. 37 | 1. Понятный [каталог](https://refactoring.guru/ru/design-patterns/catalog) паттернов проектирования. 38 | 2. [Паттерны в контексте игр](https://github.com/jabocrack1/game-programming-patterns). 39 | 3. Про [децентрализованные](https://vas3k.ru/blog/363/) системы. 40 | 4. Блокчейн просто: [раз](https://vas3k.ru/blog/blockchain/) и [два](https://vas3k.ru/blog/ethereum/). 41 | 5. Про [рекомендательные](https://vas3k.ru/blog/355/) системы. 42 | 6. [vas3k.ru](https://vas3k.ru). 43 | 7. Записи [школы бекенд разработки](https://academy.yandex.ru/posts/15-lektsiy-po-bekend-razrabotke) 44 | от Яндекса. 45 | 46 | ##### Всякие удобные онлайн-сервисы 47 | 48 | 0. Работа с [бинарными файлами](https://hexed.it). 49 | 1. [Тут](https://matworld.ru/calculator/perevod-chisel.php) норм числа в разных системах счисления гонять. 50 | 2. [Инструмент](https://graphonline.ru) для рисования графов. 51 | 3. [Определение](http://detexify.kirelabs.org/classify.html) нарисованного символа в Latex. 52 | 4. [Матричный калькулятор](https://matrixcalc.org/). 53 | 5. [Joe Schmoe](https://joeschmoe.io). 54 | 6. [explainshell](https://explainshell.com). 55 | 7. [Разные генераторы](https://generator-online.com). 56 | 8. [programming motherfucker](http://programming-motherfucker.com/become.html#C%20/%20C++). 57 | -------------------------------------------------------------------------------- /general/miscellanious.md: -------------------------------------------------------------------------------- 1 | # miscellanious 2 | 3 | 1. Концепция написания LRU-кеша. 4 | 2. Логика написания структуры данных, основанной на хешировании. 5 | 3. Источники. 6 | 7 | ## Концепция написания LRU-кеша 8 | 9 | LRU-кеш представляет из себя самую простую версию кеша, 10 | которая хранит некоторое количество k последних запрошенных 11 | значений. 12 | 13 | Рассмотрим идею того, как можно эффективно реализовать 14 | такую структуру(этот подход является эффективным и находит 15 | применение в других местах, например в хеш-таблицах). 16 | 17 | Пусть мы хотим хранить некоторый тип T, который 18 | должен быть хешируемым. 19 | 20 | Будем хранить двусвязный список, который будет хранить 21 | k последних запрошенных элементов, причём 22 | чем ближе элемент к концу, тем раньше всего он запрашивался( 23 | соответственно, чем ближе к началу, тем позже). 24 | Заведём хеш таблицу, которая будет ставить в соответствие 25 | элементу итератор на ноду листа, в которой на данный момент 26 | находится текущий элемент. 27 | Как только мы получаем итератор на ноду, мы переставляем её 28 | в начало листа. 29 | Если же элемента в листе нет, удаляем последний элемент листа, 30 | а в начало вставляем текущий. 31 | Таким образом амортизированно за O(1) мы умеем делать все операции, 32 | достигнув требуемого результат. 33 | 34 | [C++](https://github.com/dasfex/ProgrammingPractice/blob/master/cpp/algos/lrucache.h), 35 | [Golang](https://github.com/dasfex/ProgrammingPractice/blob/master/golang/lrucache.go). 36 | 37 | ## Логика написания структуры данных, основанной на хешировании. 38 | 39 | Для начала выделим память для одного бакета. 40 | Будем добавлять элементы в структуру, пока 41 | max_load_factor = кол-во элементов / кол-во бакетов 42 | не превысит некоторое значение, например 2. 43 | Как только это происходит, увеличиваем кол-во бакетов 44 | в какое-то кол-во раз(опять же можно в 2) и переносим все прошлые 45 | элементы в новые бакеты. 46 | Так же во время релокации можно сменить и хеш-функцию. 47 | 48 | Для разрешения коллизий можно использовать любой удобный способ 49 | адресации: открытый или закрытый. 50 | 51 | Однако можно немного модифицировать нашу структуру, 52 | чтобы обход всех элементов происходил на за время от кол-ва бакетов, 53 | а за время от кол-ва самих элементов, используя идею из LRU-кеша. 54 | 55 | Будет параллельно хранить двунаправленный лист, в котором будем собственно хранить 56 | сами элементы, а в бакете будем хранить не элемент, 57 | а итератор на ноду листа, в которой начинается цепочка элементов с 58 | подходящим хешом, а так же можно количество элементов с таким хешом. 59 | Как только приходит новый элемент, мы, вычисляя хеш, получаем ноду, 60 | которая является первой в цепочке элементов с конкретным хешом, 61 | перед ней вставляем новую ноду, а в бакете кроме увеличения значения 62 | перезапоминаем прошлую ноду на только что вставленную. 63 | Если мы вставляем элемент с таким хешом впервые, 64 | то можно новую ноду вставлять например в конец листа(или в начало, как удобно). 65 | Таким образом, в случае, когда нам потребуется обойти все существующие 66 | элементы, мы просто обойдём этот лист за кол-во элементов. 67 | 68 | ## Источники 69 | 1. [Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step]( 70 | https://www.youtube.com/watch?v=ncHmEUmJZf4). 71 | 2. [Ускорение LRU-cache]( 72 | https://www.youtube.com/watch?v=60XhYzkXu1M). 73 | 74 | -------------------------------------------------------------------------------- /cpp/ub.md: -------------------------------------------------------------------------------- 1 | # Undefined behaviour(неопределённое поведение) 2 | 3 | Неопределённое поведение — поведение, которое может возникать 4 | в результате использования ошибочных программных конструкций или некорректных данных, 5 | на которые Международный Стандарт не налагает никаких требований. 6 | Неопределенное поведение также может возникать в ситуациях, не описанных в Стандарте явно. 7 | 8 | Тут я попробую собрать примеры очевидного(и не очень) неопределённого поведения в C++. 9 | 10 | ### Захват переменных в лямбда-функции 11 | 12 | Давайте представим вот такую ситуацию: 13 | ```cpp 14 | class Div { 15 | int divisor; 16 | 17 | public: 18 | Div(int div) : divisor(div) {} 19 | 20 | auto get() { 21 | return [&](int n) {return n % divisor;}; 22 | } 23 | }; 24 | ... 25 | auto f = Div(10).get(); 26 | auto res = f(12); 27 | ``` 28 | 29 | Получаем ub. 30 | Кажется, что тут очевидна проблема(захват переменных по ссылке). 31 | Давайте исправим: 32 | ```cpp 33 | ... 34 | auto get() { 35 | return [=](int n) {return n % divisor;}; 36 | } 37 | ... 38 | ``` 39 | 40 | Вроде как всё исправили. 41 | Однако тут тоже ub. 42 | 43 | Почему же? 44 | 45 | Потому что время жизни переменной(```divisor```) меньше, чем время жизни лямбда-функции. 46 | 47 | Вы скажете, что мы же захватили её по значению! 48 | Но это неправда, ведь члены класса не захватываются лямбдой. 49 | В таких случаях захватываются лишь значения в локальной области видимости функции, 50 | потому при вызове ```f(12)``` ```divisor``` скорее всего не существует, что и ведёт к ub. 51 | 52 | Поправить это можно вот так: 53 | ```cpp 54 | ... 55 | auto get() { 56 | return [divisor = divisor](int n) {return n % divisor;}; 57 | } 58 | ... 59 | ``` 60 | 61 | Также стоит помнить про неявный захват ```*this```. 62 | 63 | Ну и вообще вот 64 | [тут](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f53-avoid-capturing-by-reference-in-lambdas-that-will-be-used-non-locally-including-returned-stored-on-the-heap-or-passed-to-another-thread) 65 | можно почитать. 66 | 67 | ### Ub при знаковом переполнении 68 | 69 | Можно почитать вот [тут](https://codeforces.com/blog/entry/45144). 70 | 71 | ### Dangling reference 72 | 73 | Возврат ссылки на несуществующий объект: 74 | ```cpp 75 | int& f(int x) { 76 | int y = x + 1; 77 | return y; 78 | } 79 | ``` 80 | ### reinterpret_cast 81 | 82 | Его конечно не надо юзать, но всё же факт есть факт. 83 | 84 | Предположим у нас есть ```double``` и мы хотим трактовать первых 4 его байта как ```int```: 85 | ```cpp 86 | double x = 123.2312; 87 | std::cout << reinterpret_cast(x); 88 | ``` 89 | Однако так не скомпилируется, т.к. мы не создаём новый объект, мы просто хотим посмотреть 90 | на существующий под другим углом. 91 | Потому надо кастить к ```int&```: 92 | ```cpp 93 | std::cout << reinterpret_cast(x); 94 | ``` 95 | 96 | Можно ещё вот так: 97 | ```cpp 98 | int* p = reinterpret_cast(x); 99 | ``` 100 | Так мы посмотрим на все 8 байт. 101 | Однако это всё ub (strict aliasing rule). 102 | 103 | ### Инициализация переменных во вложенных скоупах 104 | 105 | ```cpp 106 | string s = "123"; 107 | { 108 | string s /*PoD*/ = s; 109 | } 110 | ``` 111 | Можно подумать, что вторая переменная ```s``` будет инициализирована первой, но 112 | она будет пытаться инициализироваться самой собой. 113 | Причём очевидно ub, т.к. вы пытаетесь инициализировать объект из ещё несозданного. 114 | 115 | ### Неопределённый порядок выполнения 116 | 117 | Стоит быть аккуратным с выражениями вида: 118 | ```cpp 119 | i = i++ + i++; 120 | ``` 121 | Потому что тут и правда неопределён порядок выполнения операций. 122 | 123 | ### Выход указателя за границы массива 124 | 125 | Можно почитать вот [тут](https://stackoverflow.com/questions/10473573/why-is-out-of-bounds-pointer-arithmetic-undefined-behaviour). 126 | 127 | ### Incorrect printf use 128 | 129 | ```cpp 130 | printf("%d%", double(5)); 131 | ``` 132 | Результат такого выражения не определён и зависит от платформы. 133 | 134 | Объяснение: [belaycpp.com/2021/08/31/yet-another-reason-to-not-use-printf-or-write-c-code-in-general/](https://belaycpp.com/2021/08/31/yet-another-reason-to-not-use-printf-or-write-c-code-in-general/). 135 | 136 | ### Неверная перегрузка операторов 137 | 138 | Рассмотрим простой пример, когда неверно перегружен оператор ```<```: 139 | ```cpp 140 | bool operator<(A& rhs, A& lhs) { 141 | return rhs.val <= lhs.val; 142 | } 143 | ``` 144 | Подобная ситуация может привести к неопределённому поведению(например при использовании в сортировке), 145 | т.к. не соблюдается транзитивность операции( 146 | если ```a < b``` и ```b < c```, то должно быть и ```a < c```, 147 | но при указанном варианте перегрузки может произойти сравнение 148 | ```c < a```, которое окажется верным, что всё ломает 149 | ). 150 | 151 | ### Mem 152 | Использование неверной маски типа в ```printf``` 153 | является неопределённым поведением. 154 | 155 | ![mem](img/mem1.jpg) 156 | 157 | ### Ссылки 158 | 1. CppCon 2019. Timur Doumler. 159 | [“Type punning in modern C++”]( 160 | https://www.youtube.com/watch?v=_qzMpk-22cc). -------------------------------------------------------------------------------- /general/highload.md: -------------------------------------------------------------------------------- 1 | # Проектирование высоконагруженных систем и около 2 | 3 | ### talks 4 | 0. Highload++ 2014. Андрей Аксёнов. 5 | [Выбираем поиск умом головы]( 6 | https://www.youtube.com/watch?v=gJXWqkf6CX8). 7 | 1. Highload++ 2015. Константин Осипов. 8 | [Что особенного в СУБД для данных в оперативной памяти]( 9 | https://www.youtube.com/watch?v=yrTF3qH8ey8). 10 | 2. Highload++ 2017. Дмитрий Егоров. 11 | [Как переписать с нуля базу данных личных сообщений ВКонтакте]( 12 | https://www.youtube.com/watch?v=UGbLNJiJmQo). 13 | 3. Highload++ 2017. Андрей Аксёнов. 14 | [Почему оно не находится]( 15 | https://www.youtube.com/watch?v=wCGBTjHikwA). 16 | 4. Highload++ 2017. Андрей Аксёнов. 17 | [Sphinx 3.0, поиск 15 лет спустя]( 18 | https://www.youtube.com/watch?v=uKlGypo8hFY). 19 | 5. Highload++ 2017. Андрей Аксёнов. 20 | [Хочу всё сжать]( 21 | https://www.youtube.com/watch?v=wDgD4gokEis). 22 | 6. SREcon 2019. Simon Eskildsen. 23 | [Advanced Napkin Math]( 24 | https://www.youtube.com/watch?v=IxkSlnrRFqc). 25 | 7. Highload++ 2021. Андрей Аксёнов. 26 | [Прикладная эзотерика]( 27 | https://www.youtube.com/watch?v=OQASAuuZvS8). И моя [статья на хабре](https://habr.com/ru/articles/673776/) на аналогичную тему. 28 | 8. Highload++ Foundation 2022. Андрей Аксёнов. 29 | [Как выжать 1 000 000 RPS]( 30 | https://www.youtube.com/watch?v=UyRBouT6vZQ). 31 | 9. Highload++ Foundation 2022. Александр Царьков. 32 | [Поиск GPS-аномалий среди сотен тысяч водителей]( 33 | https://www.youtube.com/watch?v=6JIfhShK3Zc). 34 | 10. Highload++ Foundation 2022. Денис Исаев. 35 | [Как мы ускорили Яндекс Go на несколько секунд]( 36 | https://www.youtube.com/watch?v=229RE8fwMNs). 37 | 11. Highload++ Foundation 2022. Иван Максимов. 38 | [Как мы готовили поиск в Delivery Club]( 39 | https://www.youtube.com/watch?v=0M0gDXBauD4). 40 | 12. Saint Highload++ 2022. Андрей Аксёнов. 41 | [Про историю и будущее поиска]( 42 | https://www.youtube.com/watch?v=mNgUwoAbnJ4). 43 | 13. Saint Highload++ 2022. Даниил Подольский. 44 | [Под красным флагом: как инженер может понять, что в проекте происходит что-то не то]( 45 | https://www.youtube.com/watch?v=TcpdQKqZ_zo). 46 | 14. Saint Highload++ 2022. Юлия Белозёрова. 47 | [Как понять, что проекту плохо, если ты инженер]( 48 | https://www.youtube.com/watch?v=1jGROVPKRtU). 49 | 15. Saint Highload++ 2022. Артур Филатенков. 50 | [Векторный поиск в ClickHouse]( 51 | https://www.youtube.com/watch?v=0fT9uMV8tr0). 52 | 16. Highload++ 2022. Илья Кучумов. 53 | [Как достать всё что угодно со всего интернета]( 54 | https://www.youtube.com/watch?v=05jGuGT_nkY&list=PLH-XmS0lSi_ygFVuLJdoFcDRSjeGTbFvO&index=12). 55 | 17. Highload++ 2023. Андрей Ривкин. 56 | [Как выйти в опенсорс и не сойти с ума: опыт YTsaurus]( 57 | https://www.youtube.com/watch?v=Z7kv8tYVHx0). 58 | 18. Highload++ 2023. Айдар Гилажев. 59 | [Как эффективно ранжировать весь товарный Рунет]( 60 | https://www.youtube.com/watch?v=wNpzD_1SRn8). 61 | 19. Highload++ 2023. Александр Кирсанов. 62 | [Математический хайлоад: большие, очень большие и немыслимо большие числа]( 63 | https://www.youtube.com/watch?v=QQoZrqdvh6g). 64 | 20. Saint Highload++ 2024. Андрей Бородин. 65 | [Про UUID v.7]( 66 | https://www.youtube.com/watch?v=z4EvhdwHqgc). 67 | 68 | ### Статьи 69 | 1. [The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)]( 70 | https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/). 71 | 2. [Под капотом у Emoji](https://habr.com/ru/companies/itelma/articles/549366/). 72 | 3. [Proper Documentation](https://vadimkravcenko.com/shorts/proper-documentation/?ref=members.vadimkravcenko.com). 73 | 4. [Better Software Engineering teams — Structures, roles, responsibilities and comparison with common approaches](https://medium.com/geekculture/better-software-engineering-teams-structures-roles-responsibilities-and-comparison-with-common-fb5c3161c13d). 74 | 5. [Backend side architecture evolution](https://medium.com/@iamprovidence/backend-side-architecture-evolution-n-layered-ddd-hexagon-onion-clean-architecture-643d72444ce4). 75 | 6. [Заблуждения программистов о времени](https://habr.com/ru/articles/703360/). 76 | 7. [A tour of metaprogramming models for generics](https://thume.ca/2019/07/14/a-tour-of-metaprogramming-models-for-generics/) и [перевод на русский](https://habr.com/ru/companies/vk/articles/461321/). 77 | 8. [Про succinct data structures](https://habr.com/ru/companies/vk/articles/479822/). 78 | 9. [Architecture antipatterns](https://architecture-antipatterns.tech/?ref=thisnotes). 79 | 10. [Про ретраи](https://habr.com/ru/companies/yandex/articles/762678/). 80 | 11. [Notes on Distributed Systems for Young Bloods](https://www.somethingsimilar.com/2013/01/14/notes-on-distributed-systems-for-young-bloods/). 81 | 12. [Things I Wished More Developers Knew About Databases](https://rakyll.medium.com/things-i-wished-more-developers-knew-about-databases-2d0178464f78). 82 | 13. [Examples of Great URL Design](https://blog.jim-nielsen.com/2023/examples-of-great-urls/). 83 | 14. [A Quick(ish) Introduction to Tuning Postgres](https://byteofdev.com/posts/tuning-postgres-intro/). 84 | -------------------------------------------------------------------------------- /general/protocols.md: -------------------------------------------------------------------------------- 1 | # Протоколы 2 | 3 | ### Basics 4 | 5 | Сети в основном построены по принципу packet-switching, т.е. минимальная единица информации, которой обмениваются машины в сети - пакет. 6 | Пакет - это просто какой-то массив с байтами. Типичный размер пакета до 1200 байт(хотя специфические могут быть и больше). 7 | 8 | У каждой машины внутри сети есть адрес. Адрес бывает: 9 | + 192.168.1.1 - 4 байта в случае IPv4; 10 | + 1b00:1261:1204:c01::33 - 16 байт в случае IPv6. 11 | 12 | 0.0.0.0 - специальный адрес, означающий, что клиент/сервер принимает запросы со всех адресов. 13 | 14 | В пакете хранится адрес получателя и адрес отправителя. 15 | 16 | На каждом хосте(сервере) запущен не один сервис. 17 | Чтобы различать, какому сервису отправлен пакет, используются порты. 18 | В рамках одного сервера могут работать несколько сервисов, причём каждый на своём порту. 19 | Таким образом можно запускать можно сервисов на одном сервере и с помощью порта понимать, кому конкретно отправляется пакет. port - число от 0 до 65536. 20 | 21 | Однако пользователи/сервера идентифицируются не просто адресом. 22 | Если бы сервис пытался отличать пользователя только по адресу, то получается, что все одинаковые адреса являлись одним пользователем. 23 | Тут возникает проблема с тем, кому отправлять ответ в случае, если у нас открыто несколько вкладок. Адрес же один. Потому каждый пользователь(вкладка в данном случае) также имеет свой порт и идентифицируется как (ip, port). Получим, что у разных вкладок разные порты. 24 | 25 | Работать с адресом напрямую неудобно, т.к. одному домену могут соответствовать несколько адресов. 26 | Для устранения этой проблемы была придумана DNS(Domain Name System). 27 | Она умеет отвечать на запрос "какие адреса соответствуют доменному имени". 28 | 29 | В консоли linux можно использовать следующую команду: 30 | ``` 31 | $ dig vk.com 32 | ;; ANSWER SECTION: 33 | vk.com. 759 IN A 87.240.139.194 34 | vk.com. 759 IN A 93.186.225.208 35 | vk.com. 759 IN A 87.240.190.78 36 | vk.com. 759 IN A 87.240.137.158 37 | vk.com. 759 IN A 87.240.190.67 38 | vk.com. 759 IN A 87.240.190.72 39 | ``` 40 | 41 | Есть разные технологии передачи данных(3G, 4G, WiFi, Ethernet(проводное подключение)). 42 | 43 | Протоколы передачи не являются монолитным объектом. 44 | Они существуют в несколько слоёв, причём друг о друге ничего не знают, что делает систему довольно гибкой и удобной для модификации. 45 | На практике это выглядит так: 46 | 47 | ![protocols_stack](./img/protocols_stack.jpg) 48 | 49 | UDP просто добавляет слой портов, т.е. записывает на пакет оставшуюся информацию и кидает в сеть. 50 | 51 | UDP: 52 | + никаких гарантий на доставку; 53 | + передаёт пакеты до ~1000 байт(причём максимальный размер непонятен, т.к. протокол ничего не гарантирует; тут надо ещё каким-то образом узнавать максимальный размер пакета); 54 | + пакеты могут дойти в случайном порядке; 55 | + DNS работает поверх UDP. 56 | 57 | TCP - протокол с гарантированной доставкой. 58 | 59 | TCP: 60 | + передаёт поток байт; 61 | + для использования требуется установить соединение; 62 | + TCP-соединение - два потока байт(запись и чтение). 63 | 64 | > Пусть есть некоторый сервер и клиент, котороый хочет с ним пообщаться. 65 | > Клиент создаёт подключение(обменялись некоторым количеством пакетов и теперь считают, что у них есть подключение). 66 | > Теперь протокол создают "иллюзию" наличия двух каналов данных(в обе стороны), причём под каналом имеется в виду просто непрерывнй поток байт. 67 | > При передаче в случае утери данных с помощью протокола клиент как-то понимает, что некоторый кусок информации был утерян и просто пересылает его, после чего сервер читает байты как будто ничего не терялось, просто непрерывными целыми кусками(jднако 100%-й гарантии никогда нет, ведь может упасть вся система или пропасть соединение). 68 | 69 | Если посмотреть на пакет, в нём есть некоторый набор заголовков(от Ethernet, IP, протокола), а после идут сами данные. 70 | 71 | В основном UDP используется в случаях, когда нужно низкоуровневое API, в случаях, если в вашем проекте свои протоколы, криптография и вы готовы взять ответственность за доставку на себя, но при этом хотите максимально всё оптимизировать. 72 | 73 | ### Как сеть выглядит с точки зрения приложения(TCP) 74 | 75 | TCP/IP - это некоторое API, которое реализовано в операционной системе, и представляет собой набор системных вызовов. 76 | Чтобы с API взаимодействовать, существует socket. 77 | Он может выполнять две роли: клиента и сервера. 78 | 79 | Рассмотрим роль клиента. 80 | 81 | Предположим, что мы как клиент хотим подключится к некоторому сайту. Нам нужно создать TCP-соединение и сообщить ОС, куда это соединение направлено. 82 | 83 | 1. Сначала всегда создаём socket. 84 | Тут нужно указать, по какому протоколу будем работать: 85 | ``` 86 | create TCP socket 87 | ``` 88 | 89 | 2. Дальше его нужно подключить к серверу по ip-адресу и порту: 90 | ``` 91 | connect(ip, port) 92 | ``` 93 | 94 | Тут ОС выделяет свободный порт и начинает процесс подключения(обмен примерно 2мя пакетами в обе стороны). 95 | Если всё успешно, соединение считается установленным. 96 | 97 | 3. Теперь можно отправлять и получать данные: 98 | ``` 99 | send(bytes[]) 100 | recieve() 101 | ``` 102 | 103 | 4. Закрываем соединение: 104 | ``` 105 | close(). 106 | ``` 107 | -------------------------------------------------------------------------------- /cpp/sfinae.md: -------------------------------------------------------------------------------- 1 | # SFINAE 2 | 3 | Иногда требуется делать что-то в зависимости от того, есть ли у класса некоторый метод( 4 | например ```allocator_traits``` пытается понять про методы ```construct``` и ```destruct```). 5 | 6 | ### Идея и простейший пример 7 | 8 | ```cpp 9 | template 10 | auto f(const T& x) -> decltype(T().size()) { 11 | std::cout << 1; 12 | return 1; 13 | } 14 | 15 | size_t f(...) { 16 | std::cout << 2; 17 | return 2; 18 | } 19 | ... 20 | std::vector v{1, 2, 3}; 21 | f(v); 22 | f(1); 23 | ``` 24 | Что будет при выполнении такой программы? 25 | Выводом будет ```12```. 26 | 27 | Давайте разберём по пунктам мною написанное. 28 | 29 | При вызове ```f(1)``` компилятор очевидно предпочтёт первую версию, т.к. она более частная, 30 | но споткнётся на выводе типа, ведь у ```int``` нет метода ```size()```. 31 | Почему же просто не выкинуть compile error? 32 | Логика следующая: если не получается инстанцировать функцию(не тело, а именно сигнатуру), 33 | компилятор не выдаст ошибку, а просто выкинет её из кандидатов и будет искать заново. 34 | 35 | ## Некоторые интересные техники 36 | 37 | 38 | ### Проверка наличия метода в классе 39 | 40 | ```cpp 41 | template 42 | struct has_f { 43 | private: 44 | template 45 | constexpr static auto f(int) -> 46 | decltype(std::declval().f(std::declval()...), int()) { 47 | return 1; 48 | } 49 | 50 | template 51 | constexpr static char f(...) { 52 | return 0; 53 | } 54 | 55 | public: 56 | static const bool value = f(0); 57 | }; 58 | 59 | template 60 | bool has_f_v = has_f::value; 61 | ... 62 | struct T { 63 | void f(int); 64 | }; 65 | 66 | std::cout << has_f_v << std::endl; 67 | std::cout << has_f_v << std::endl; 68 | ``` 69 | 70 | Обратим внимание на несколько вещей: 71 | 1. Мы пишем ```decltype```, после чего через запятую указываем тип. 72 | Это делается для того, чтобы был выведен нужный нам тип, но и требуемое выражение 73 | было проверено. 74 | Такой способ называется comma trick. 75 | 2. Если не указать отдельные шаблонные аргументы для ```f```, то в случаях, 76 | когда метода нет, мы получим ошибку компиляции, т.к. инстанцирование произойдёт 77 | во время инстанцирования класса, а не функции. 78 | 3. ```std::declval``` используем для случая, 79 | если у типа не окажется конструктора по умолчанию. 80 | 81 | Однако текущая форма не совсем является корректным примером метапрограммирования, 82 | т.к. метапрограммирование происходит над типами. 83 | Модернизируем: 84 | ```cpp 85 | template 86 | struct integral_constant { 87 | static const T value = value_; 88 | }; 89 | 90 | struct true_type : public integral_constant {}; 91 | struct false_type : public integral_constant {}; 92 | 93 | template 94 | struct has_f { 95 | private: 96 | template ().f(std::declval()...))> 98 | static true_type f(int); 99 | 100 | template 101 | static false_type f(...); 102 | 103 | public: 104 | using type = decltype(f(0)); 105 | }; 106 | 107 | template 108 | bool has_f_v = std::is_same_v::type, true_type>; 109 | ``` 110 | Тут мы сделали несколько улучшений: 111 | все вычисления производятся над типами(более чистое метапрограммирование); 112 | переход к значению происходит в самом конце; 113 | тела функций не нужны, т.к. всё решается только с помощью сигнатур. 114 | 115 | Стоит понимать, что не удастся реализовать метафункцию, которая будет принимать 116 | название метода как параметр. 117 | Разве что только с помощью макросов: 118 | ```cpp 119 | #define HAS_METHOD(NAME) \ 120 | template \ 121 | struct has_##NAME { \ 122 | private: \ 123 | template ().NAME(std::declval()...))> \ 125 | static true_type f(int); \ 126 | template \ 127 | static false_type f(...); \ 128 | public: \ 129 | using type = decltype(f(0)); \ 130 | }; \ 131 | template \ 132 | bool has_##NAME##_v = std::is_same_v::type, true_type>; 133 | ``` 134 | 135 | ### ```std::enable_if``` 136 | 137 | Предположим, у нас есть две функции: 138 | ```cpp 139 | template 140 | void f(const T&) { 141 | std::cout << 1; 142 | } 143 | 144 | void f(...) { 145 | std::cout << 2; 146 | } 147 | ``` 148 | И мы хотим, чтобы в первую, например, мы попадали только тогда, когда тип ```T``` 149 | является классом или, например, он может быть сконструирован от некоторых аргументов. 150 | Т.е. для случаев, когда тип удовлетворяет некоторому метапредикату. 151 | ```cpp 152 | template >> 153 | void f(const T&) { 154 | std::cout << 1; 155 | } 156 | ``` 157 | Т.е. если некоторое метаусловие не выполняется, то мы хотим убрать функцию 158 | из кандидатов при выборе перегрузки. 159 | 160 | Реализуем его c помощью следующего трейта: 161 | ```cpp 162 | template 163 | struct type_is { using type = T; } 164 | 165 | template 166 | struct enable_if : type_is {}; 167 | 168 | template 169 | struct enable_if {}; 170 | 171 | template 172 | using enable_if_t = typename enable_if::type; 173 | ``` 174 | Т.е. в случае ```true``` мы попадём в специализацию, в которой есть ```type```, 175 | а иначе его не будет и произойдёт неудачная шаблонная подстановка. 176 | 177 | Зачем может быть нужен тип в ```enable_if```? 178 | Иногда мы хотим получить различные типы в зависимости от выполнения 179 | некоторых условий: 180 | ```cpp 181 | template 182 | std::enable_if_t, long long int> f(T val) {...} 183 | 184 | template 185 | std::enable_if_t, double> f(T val) {} 186 | ``` 187 | 188 | ### Как это было раньше 189 | 190 | Во времена, когда не существовало ключевого слова ```constexpr```, 191 | писали вот так: 192 | ```cpp 193 | int const x = 1; 194 | ``` 195 | -------------------------------------------------------------------------------- /cpp/additional.md: -------------------------------------------------------------------------------- 1 | # Additional C++ 2 | 3 | ### talks 4 | 1. CppCon 2014: Walter E. Brown. 5 | [Modern Template Metaprogramming: A Compendium, Part I]( 6 | https://www.youtube.com/watch?v=Am2is2QCvxY&feature=youtu.be). 7 | [Modern Template Metaprogramming: A Compendium, Part II]( 8 | https://www.youtube.com/watch?v=a0FliKwcwXE). 9 | 2. CppCon 2015. Andrei Alexandrescu. 10 | [“Declarative Control Flow"]( 11 | https://www.youtube.com/watch?v=WjTrfoiB0MQ). 12 | 3. CppCon 2015. Andrei Alexandrescu. 13 | [std::allocator](https://www.youtube.com/watch?v=LIb3L4vKZ7U). 14 | 4. CppCon 2016: Jason Turner. 15 | [“Practical Performance Practices"](https://www.youtube.com/watch?v=uzF4u9KgUWI&t=3212s). 16 | 5. CppCon 2016. Nicholas Ormrod. 17 | [“The strange details of std::string at Facebook"]( 18 | https://www.youtube.com/watch?v=kPR8h4-qZdk). 19 | 6. CppCon 2016. Stephan T. Lavavej. 20 | [“tuple<>: What's New and How it Works"]( 21 | https://www.youtube.com/watch?v=JhgWFYfdIho). 22 | 7. CppCon 2016. Antony Polukhin. 23 | [C++14 Reflections Without Macros, Markup nor External Tooling..]( 24 | https://www.youtube.com/watch?v=abdeAew3gmQ). 25 | 8. CppCon 2017. Matt Godbolt. 26 | [What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid]( 27 | https://www.youtube.com/watch?v=bSkpMdDe4g4). 28 | 9. CppCon 2017. Matt Kulukundis. 29 | [Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step]( 30 | https://www.youtube.com/watch?v=ncHmEUmJZf4). 31 | 10. CppCon 2017. Arthur O'Dwyer. 32 | [A Soupçon of SFINAE]( 33 | https://www.youtube.com/watch?v=ybaE9qlhHvw). 34 | 11. St. Petersburg C++ User Group 2018. Тимур Думлер и Дмитрий Кожевников. 35 | [Парсинг C++]( 36 | https://www.youtube.com/watch?v=7co2tZ4tMfs). 37 | 12. C++ Russia 2018. Фёдор Короткий. 38 | [Память -- идеальная абстракция]( 39 | https://www.youtube.com/watch?v=i87W3KyZgPw) 40 | 13. CppCon 2018. Andrei Alexandrescu. 41 | [“Expect the expected”]( 42 | https://www.youtube.com/watch?v=PH4WBuE1BHI). 43 | 14. CppCon 2018. Walter E. Brown. 44 | [“C++ Function Templates: How Do They Really Work?”]( 45 | https://www.youtube.com/watch?v=NIDEjY5ywqU). 46 | 15. CppCon 2018. Jason Turner. 47 | [Surprises in Object Lifetime]( 48 | https://www.youtube.com/watch?v=uQyT-5iWUow) 49 | 16. CppCon 2018. Timur Doumler. 50 | [Can I has grammar?]( 51 | https://www.youtube.com/watch?v=tsG95Y-C14k). 52 | 17. CppCon 2018. Nicolai Josuttis. 53 | [The Nightmare of Initialization in C++]( 54 | https://www.youtube.com/watch?v=7DTlWPgX6zs). 55 | 18. CppCon 2019. Timur Doumler. 56 | [Type punning in modern C++]( 57 | https://www.youtube.com/watch?v=_qzMpk-22cc). 58 | 19. C++ Russia 2019. Timur Doumler. 59 | [Initialisation in modern C++]( 60 | https://www.youtube.com/watch?v=2jJumNzcp6Y). 61 | 20. CppCon 2019. Marshall Clow. 62 | [std::midpoint? How Hard Could it Be?](https://www.youtube.com/watch?v=sBtAGxBh-XI). 63 | 21. Олег Фатхиев. 64 | [Эволюция метапрограммирования: как правильно работать со списками типов]( 65 | https://www.youtube.com/watch?v=ZUmc45Njs9U). 66 | 22. Михаил Матросов. 67 | [Спецификаторы, квалификаторы и шаблоны]( 68 | https://www.youtube.com/watch?v=G_jcBrrYPAs). 69 | 23. Антон Полухин. 70 | [Ускорение LRU-cache]( 71 | https://www.youtube.com/watch?v=60XhYzkXu1M). 72 | 24. Михаил Матросов. 73 | [Как объявить константу в C++?]( 74 | https://www.youtube.com/watch?v=GPAGiXNVED4). 75 | 25. Сергей Фёдоров. 76 | [Шаблоны C++ и базы данных](https://www.youtube.com/watch?v=7qCtCXxpgfw). 77 | 26. C++ Zero Cost Conf 2021. Timur Doumler. 78 | [Использование стандартной библиотеки С++ для обработки сигналов в real-time]( 79 | https://www.youtube.com/watch?v=8GlwkWxf3hk&t=3496s). 80 | 27. CppCon 2021. Jody Hagins. 81 | [Template Metaprogramming: Practical Application]( 82 | https://www.youtube.com/watch?v=4YC6_77-iEY). 83 | 28. CppCon 2021. Jason Turner. 84 | [Your New Mental Model of constexpr]( 85 | https://www.youtube.com/watch?v=MdrfPSUtMVM). 86 | 29. CppCon 2021. Andrei Alexandrescu. 87 | [Embracing (and also Destroying) Variant Types Safely]( 88 | https://www.youtube.com/watch?v=va9I2qivBOA). 89 | 30. CppCon 2021. Daisy Hollman. 90 | [Why You Should Write Code That You Should Never Write]( 91 | https://www.youtube.com/watch?v=15etE6WcvBY). 92 | 31. Highload++ Foundation 2022. Никита Старичков. 93 | [Как мы устроили переезд 10+млн строк С++ кода на новый стандарт]( 94 | https://www.youtube.com/watch?v=R_HdlLuGk40). 95 | 32. CppCon 2022. Herb Sutter. 96 | [Can C++ be 10x Simpler & Safer?]( 97 | https://www.youtube.com/watch?v=ELeZAKCN4tY) (about cpp2). 98 | 33. CppCon 2022. Timur Doumler. 99 | [C++ Lambda Idioms]( 100 | https://www.youtube.com/watch?v=xBAduq0RGes). 101 | 34. CppCon 2022. Andrei Alexandrescu. 102 | [Reflection in C++ - Past, Present, and Hopeful Future]( 103 | https://www.youtube.com/watch?v=YXIVw6QFgAI). 104 | 35. CppCon 2022. Daisy Hollman. 105 | [Cute C++ Tricks, Part 2.5 of N]( 106 | https://www.youtube.com/watch?v=gOdcNko2xc8). 107 | 36. CppCon 2022. David Stone. 108 | [Lightning Talk: How to Win at Coding Interviews]( 109 | https://www.youtube.com/watch?v=y872bCqQ_P0&list=PLHTh1InhhwT6U_8ehqxpB7-O1KF_5WwC4&index=2). 110 | 37. CppCon 2022. Tomer Vromen. 111 | [Lightning Talk: Finding the Average of 2 Integers](https://www.youtube.com/watch?v=rUt9xcPyKEY&list=PLHTh1InhhwT6U_8ehqxpB7-O1KF_5WwC4&index=9). 112 | 38. CppCon 2023. Andrei Alexandrescu. 113 | [Robots Are After Your Job: Exploring Generative AI for C++]( 114 | https://www.youtube.com/watch?v=J48YTbdJNNc&list=PLHTh1InhhwT7gQEuYznhhvAYTel0qzl72&index=5) (about binary search and ChatGPT). 115 | 39. CppCon 2023. Danila Kutenin. 116 | [A Long Journey of Changing std::sort Implementation at Scale]( 117 | https://www.youtube.com/watch?v=cMRyQkrjEeI). 118 | 40. Meeting C++ 2023. Björn Fahller. 119 | [My favourite memory leak]( 120 | https://www.youtube.com/watch?v=LKKmPAQFNgE). 121 | 41. C++ Russia 2023. Константин Владимиров. 122 | [Семантические процессы в C++]( 123 | https://www.youtube.com/watch?v=lc3UkIZ4zOY). 124 | 42. C++ on Sea 2023. Jonathan Müller. 125 | [C++ Features You Might Not Know](https://www.youtube.com/watch?v=zGWj7Qo_POY). 126 | 43. CppCon 2023. Ali Almutawa. 127 | [Forbidden C++](https://www.youtube.com/watch?v=6Ux-YaStOtM). 128 | 44. Cpp on Sea 2023. JF Bastien. 129 | [\*(char\*)0 = 0;](https://www.youtube.com/watch?v=dFIqNZ8VbRY). 130 | 45. С++ Russia 2024. Антон Полухин. 131 | [Грязные C++ трюки из userver и Boost](https://www.youtube.com/watch?v=-pE3T6wsIrM). 132 | 46. C++ Zero Cost Conf 2024. Ваня Ходор (да, я). 133 | [NRVO: что такое и как не сломать?](https://www.youtube.com/live/DgS9p40-Xfw?si=BnuFJY_gkt1dK43z&t=13190) 134 | 47. CppCon 2024. Samuel Privett. 135 | [Why Is My C++ Build So Slow? Compilation Profiling and Visualization](https://www.youtube.com/live/Oih3K-3eZ4Y). 136 | 137 | ### Дополнительные источники 138 | 0. Очень базовый 139 | [источник](https://www.youtube.com/channel/UCtLKO1Cb2GVNrbU7Fi0pM0w) 140 | для начинающих. 141 | 1. Первая часть классного 142 | [плейлиста](https://www.youtube.com/playlist?list=PL4_hYwCyhAvazfCDGyS0wx_hvBmnAAf4h). 143 | И [вторая](https://www.youtube.com/playlist?list=PL4_hYwCyhAvYTzwME4vQoDO8ZINM5trra). 144 | 2. [Продвинутый](https://www.youtube.com/playlist?list=PL3BR09unfgcgJPQZKaacwzGmcXMtEA-19) курс 145 | в магистратуре МФТИ на базе Intel. 146 | 3. Огненный [курс](https://cpp-school.unigine.com/#video-lectures) Андрея Аксёнова про базу структур данных. 147 | 4. Три интересных сервиса: [godbolt.org](https://godbolt.org) (disassemble your code), 148 | [cppinsights.io](cppinsights.io) (показывает, во что разворачивается ваш код), 149 | [build-bench.com](build-bench.com) (можно померять время компиляции). 150 | 5. [Proposals](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/). 151 | 6. [Стандарт](https://eel.is/c++draft/). 152 | -------------------------------------------------------------------------------- /cpp/new_delete.md: -------------------------------------------------------------------------------- 1 | # new, delete 2 | 3 | ## new 4 | 5 | ### Общая форма оператора new 6 | 7 | У оператора ```new``` есть несколько версий. 8 | Стандартная выглядит вот так: 9 | ```cpp 10 | T* p = new T(x, y, z); // i.e. some args 11 | ``` 12 | 13 | В таком виде он выделяет память в куче 14 | (запрашивает у операционной системы память, а именно столько, 15 | сколько нужно для хранения одного объекта типа ```T```(```sizeof(T)```)), 16 | вызывает конструктор этого типа от аргументов на той памяти, 17 | которую дали, и возвращает указатель на эту самую память. 18 | 19 | Однако иногда нам не нужно выделять память, а только вызвать конструктор, а иногда наоборот. 20 | Но обычный ```new``` в стандартной форме делает обе эти вещи. 21 | Причём вторую часть нельзя никак переопределить. 22 | 23 | А что ещё может делать оператор ```new``` кроме этого? 24 | 25 | На самом деле у него есть ещё другие синтаксические формы. 26 | 27 | Наиболее полезной после стандартной является ```placement new```. 28 | 29 | Чтобы понять, что это такое, давайте обратимся к примеру ```std::vector```. 30 | Каким образом в нём работает ```push_back```? 31 | 32 | Пусть к нам приходит новый элемент и у нас переполняется буфер. 33 | Мы перевыделяем в 2 раза больше памяти, 34 | перемещаем уже имеющиеся элементы и конструируем на новом месте новый элемент. 35 | А что значит это "конструируем на новом месте новый элемент"? 36 | Т.е. по факту нам по указателю нового элемента нужно вызвать конструктор. 37 | 38 | Для этого существует другая форма оператора ```new``` - ```placement new```. 39 | 40 | ```cpp 41 | new(p) T(x, y, z); // type(p) = T* или reinterpret_cast 42 | ``` 43 | 44 | > Имеем ```arr``` - начало выделенной памяти, ```sz``` - размер текущего вектора. 45 | > 46 | > В простейшем варианте ```push_back``` может выглядеть так: 47 | > ```cpp 48 | > new(arr + sz) T(x, y, z); 49 | > ``` 50 | 51 | Чем это плохо? 52 | 53 | Если бы контейнеры напрямую вызывали ```new```, то были бы проблемы, например, на серверах, 54 | где системная память распределяется по-особому. 55 | 56 | Это две основных формы ```new```. 57 | Но на самом деле этот оператор может быть с любыми параметрами. 58 | 59 | ### Перегрузка new 60 | 61 | Как уже говорилось выше, ```new``` состоит из 2х частей: выделение памяти и вызов конструктора. 62 | На самом деле любая форма оператора ```new``` делает эти две вещи, просто бывает так, 63 | что первая часть переопределена таким образом, что ничего не происходит. 64 | 65 | Любая форма оператора ```new``` по стандарту вызывает глобальную функцию 66 | ```operator new``` с параметрами, которые дали, и на месте, 67 | которое возвращает эта глобальная функция, вызывает конструктор. 68 | Причём 2я часть никак не подлежит переопределению. 69 | 70 | Т.е. теперь можно понять различие между стандартной формой и ```placement new```: 71 | первая вызывает глобальную функцию с единственным параметром количества байт, 72 | а вторая форма с двумя - количество байт и указатель на место для вызова конструктора. 73 | И 2й определён так, что он ничего не делает, 74 | в то время как 1й вариант вызывает некоторый системный вызов для выделения памяти. 75 | 76 | Таким образом, когда речь идёт о перегрузке ```new``` надо понимать, 77 | что понимается под этим перегрузка функции с названием ```operator new```, 78 | и это не то же самое, что оператор ```new```. 79 | 1е лишь часть 2ого. 80 | И перегрузить можно лишь функцию, но не весь оператор. 81 | 82 | Т.е. когда мы перегружаем оператор, на самом деле можем перегрузить глобальную функцию: 83 | ```cpp 84 | void* operator new(size_t n) { 85 | std::clog << n << " bytes allocated" << std::endl; 86 | ...some code... 87 | } // например хотим логировать все динамические выделения в программе 88 | ``` 89 | 90 | Стоит помнить, что внутри перегрузки нельзя вызывать оператор ```new```, иначе попадём в рекурсию. 91 | 92 | Или например перегрузка ```placement new```: 93 | ```cpp 94 | template 95 | T* operator new(size_t n, T* p) noexcept { 96 | return p; // именно так выглядит стандартная реализация такой формы 97 | } 98 | ``` 99 | 100 | Как пример нестандартного ```new``` есть ```nothrow new```(```new```, который не бросает исключение): 101 | ```cpp 102 | new(std::nothrow) T(x, y, z); 103 | // реализация примерно такая 104 | namespace std { 105 | struct nothrow_t {} nothrow; // фиктивный тип 106 | } 107 | void* operator new(size_t n, std::nothrow_t nothrow) { 108 | ... 109 | } 110 | ``` 111 | 112 | Если у него не получилось выделить память, он вернёт ```nullptr``` 113 | (конструктор соответственно не вызовется). 114 | Обычный ```new``` бросает ```std::bad_alloc```. 115 | 116 | Ещё можно переопределить ```new``` внутри класса. 117 | Тогда все вызовы будут изменены только для конкретного класса. 118 | Ещё можно переопределить ```new []``` 119 | (тут параметр ```n``` не количество байт, а количество объектов типа ```T```). 120 | 121 | ## delete 122 | 123 | Тут всё примерно также. 124 | 125 | Оператор ```delete``` сначала вызывает деструктор, 126 | а потом вызывает глобальную функцию ```operator delete```. 127 | Первую часть переопределить нельзя, вторую можно. 128 | 129 | > Однако в C++20 появилась версия ```delete```, которая не вызывает деструктор. 130 | > 131 | > Например мы хотим запретить создавать экземпляры класса на стеке(т.е. только с помощию ```new```): 132 | > делаем деструктор приватным, а ```delete``` перегружаем так, 133 | > чтобы он вызывал некоторую публичную функцию, которая вызывает деструктор. 134 | 135 | ```cpp 136 | void operator delete(void* p, size_t n) { // n - размер одного объекта 137 | ...clear memory... 138 | } 139 | ``` 140 | 141 | Аналогично можно определять ```delete``` с пользовательскими параметрами или ```delete []```: 142 | ``` 143 | void operator delete[] (void* p, size_t n) { // n - некоторое число, которое запомнил компилятор 144 | ...clear memory... 145 | } 146 | ``` 147 | 148 | > Замечения: 149 | > 150 | > 1. Пусть есть два класса ```Base``` и ```Derived```(2й наследуется от 1ого). 151 | > Причём у каждого есть своя версия ```delete```. И мы пишем: 152 | > ```cpp 153 | > Base* p = new Derived(); 154 | > delete p; 155 | > ``` 156 | > 157 | > Для того, чтобы был вызван правильный деструктор, нужно, чтобы он был виртуальным. 158 | > А что с оператором ```delete```? 159 | > В стандарте это решено так: если есть виртуальный деструктор, 160 | > то вызывается и верная версия ```delete```. 161 | > 162 | > 2. Если мы пишем кастомный оператор ```delete```, его нельзя вызывать ```delete(...) p```, 163 | > потому если мы написали собственный оператор ```delete```, 164 | > то вызывать придётся явно вызывать функцию освобождения памяти, 165 | > а перед этим явно вызвать деструктор: ```operator delete(...) p```. 166 | 167 | Также стоит учитывать, что если мы пишем ```new``` со своими параметрами, 168 | то надо написать и ```delete``` с аналогичными, потому что в ситуации, когда 169 | в вашем ```new``` конструктор выбросит исключение, компилятор непременно 170 | попытается вызвать ```delete``` с такими же параметрами. 171 | 172 | ## Нехватка памяти 173 | 174 | Есть такая функция(это не её конкретное название, а скорее общее назначение) ```new_handler``` 175 | (обработчик ```new```). 176 | Это функция, которая вызывается, если ```new``` не смог выделить память. 177 | Т.е. ```new``` не сразу бросает ```std::bad_alloc```. 178 | Оператор сначала вызывает стандартный обработчик, который в свою очередь бросает исключение. 179 | 180 | Для работы с обработчиком есть функции ```std::get_new_handler``` и ```std::set_new_handler```. 181 | Т.е. подразумевается, что ```new_handler``` содержит некоторый код, 182 | который возможно поможет с выделением. 183 | Причём оператор ```new``` не просто вызывает эту функцию, 184 | а вызывает её в цикле, пока что-то не произойдёт. 185 | -------------------------------------------------------------------------------- /cpp/type_traits.md: -------------------------------------------------------------------------------- 1 | ## type traits 2 | 3 | ### Basics 4 | 5 | В C++11 появился заголовок `````` для работы 6 | с типами на этапе компиляции. 7 | Некоторые функции пишутся с помощью хитрых трюков и фич, но некоторые 8 | можно написать и самому. 9 | 10 | Например, давайте напишем ```is_same```(проверку во время компиляции 11 | равны ли два типа): 12 | ```cpp 13 | template 14 | struct is_same { 15 | static constexpr bool value = false; 16 | }; 17 | 18 | template 19 | struct is_same { 20 | static constexpr bool value = true; 21 | }; 22 | ``` 23 | Ещё для примера напишем ```remove_reference```: 24 | ```cpp 25 | template 26 | struct remove_reference { 27 | using type = T; 28 | }; 29 | 30 | template 31 | struct remove_reference { 32 | using type = T; 33 | }; 34 | ``` 35 | Зная, как выводятся типы шаблонов, понять, 36 | как работает такая конструкция, несложно. 37 | 38 | Аналогично пишутся ```remove_pointer, remove_const, 39 | remove_extent```(последнее позволяет убрать одно измерение у массива, 40 | т.е. ```array[][][][] -> array[][][]```(вместо ```T&``` пишется 41 | ```T[]```)). 42 | 43 | Также есть структура ```std::decay```, торая позволяет снять с 44 | типа ```T``` все квалификаторы. 45 | 46 | Аналогично можно написать ```add_const, add_reference, add_pointer``` и т.д. 47 | 48 | Вспомнив variadic templates можно написать кастомный ```is_homogeneous```: 49 | ```cpp 50 | template 51 | struct is_homogeneous { 52 | static const bool value = (std::is_same_v && ...); 53 | }; 54 | ``` 55 | 56 | Также для реализации type_trait'ов выше можно использовать 57 | вот такой трюк. 58 | Рассмотрим type_trait ```type_is```: 59 | ```cpp 60 | template 61 | struct type_is { using type = T; } 62 | ``` 63 | Чем может быть полезен такая тривиальная метафункция? 64 | Она упрощает реализацию других type_trait'ов: 65 | ```cpp 66 | template 67 | struct remove_volatile : type_is {}; 68 | 69 | template 70 | struct remove_volatile : type_is {}; 71 | ``` 72 | 73 | ### ```std::conditional``` 74 | 75 | ```std::conditional``` - выбирает тип ```T```, 76 | если ```condition``` - ```true```, и ```S``` иначе. 77 | 78 | ```cpp 79 | template 80 | struct conditional : type_is {}; 81 | 82 | template 83 | struct conditional : type_is {}; 84 | ``` 85 | 86 | С помощью ```type_is```: 87 | ```cpp 88 | template 89 | struct conditional : type_is {}; 90 | 91 | template 92 | struct conditional : type_is {}; 93 | ``` 94 | 95 | ### std::common\_type 96 | ```cpp 97 | namespace std { 98 | 99 | // напомним про declval 100 | template 101 | std::add_rvalue_reference_t declval() noexcept; 102 | 103 | template 104 | struct common_type { 105 | using type = typename common_type::type>::type; 106 | }; 107 | 108 | template 109 | struct common_type { 110 | using type = std::remove_reference_t() : declval())>; 111 | }; 112 | 113 | } // std 114 | ``` 115 | 116 | https://stackoverflow.com/questions/67959239/what-is-complexity-of-stdcommon-type 117 | 118 | ### Алиасы и шаблонные переменные 119 | 120 | Чтобы не писать каждый раз ```std::remove_reference::type``` существуют подобные алиасы: 121 | ```cpp 122 | template 123 | using remove_reference_t = typename remove_reference::type; 124 | ``` 125 | Однако в C++17 также появились шаблонные переменные, 126 | которые удобно использовать для некоторых ситуаций вроде ```std::is_same```: 127 | ```cpp 128 | template 129 | const bool is_same_v = is_same::value; 130 | ``` 131 | 132 | ### Dependent names 133 | 134 | Разберёмся, почему в примере выше с ```remove_reference_t``` мы использовали слово ```typename```. 135 | 136 | Рассмотрим несколько примеров. 137 | 138 | 1. 139 | ```cpp 140 | template 141 | struct S { 142 | using x = T; 143 | }; 144 | 145 | template <> 146 | struct S { 147 | static int x; 148 | }; 149 | 150 | template 151 | void f() { 152 | S::x * a; 153 | } 154 | ... 155 | f(); 156 | ``` 157 | Как компилятор должен распарсить выражение ```S::x * a```? 158 | 159 | Есть два варианта: объявление указателя ```a``` на тип ```S::x``` и произведение двух переменных. 160 | 161 | По дефолту считается, что в таких неоднозначных случаях будет иметься в виду переменная. 162 | Чтобы указать, что это именно тип, нужно использовать ```typename```: 163 | ```cpp 164 | typename S::x* a; 165 | ``` 166 | 167 | 2. 168 | ```cpp 169 | template 170 | struct S { 171 | template 172 | struct A {}; 173 | }; 174 | 175 | template <> 176 | struct S { 177 | static const int A = 5; 178 | }; 179 | 180 | template 181 | void g() { 182 | S::A<10> a; 183 | } 184 | ``` 185 | При компиляции этого кода мы получим ошибку: 186 | ``` 187 | 'a' was not declared in this scope 188 | ``` 189 | Тут опять же ```S::A``` парсится как переменная, а далее компилятор разбирает это выражение как 190 | оператор меньше(```<```), 10, оператор больше(```>```) и некоторая переменная ```a```. 191 | 192 | Однако если написать 193 | ```cpp 194 | typename S::A<10> a; 195 | ``` 196 | мы также получим ошибку компиляции, ведь компилятор понял, что ```S::A``` - это тип, 197 | а дальше он ожидает идентификатор(имя переменной, которую мы объявляем), а не оператор меньше. 198 | Вот тут недостаточно сказать компилятору, что это имя типа. 199 | Нужно указать, что это имя шаблонного типа. 200 | Делается это вот так: 201 | ```cpp 202 | typename S::template A<10> a; 203 | ``` 204 | 205 | 3. 206 | ```cpp 207 | template 208 | struct B { 209 | int x; 210 | }; 211 | 212 | template 213 | struct D : B { 214 | void f() { 215 | x = 1; 216 | } 217 | }; 218 | ``` 219 | При компиляции мы получим 220 | ``` 221 | 'x' was not declared in this scope 222 | ``` 223 | Однако понятно, что если бы наследование происходило не от шаблонного родителя, то переменная была бы видна. 224 | Почему же с шаблонами это не работает? 225 | 226 | Тут ```x``` - зависимое имя. 227 | И компилятор не будет ходить в шаблонного родителя, чтобы понять, чем конкретно является ```x```. 228 | Потому нужно явно указывать, откуда он берётся: 229 | ```cpp 230 | Base::x = 1; 231 | // or 232 | this->x = 1; 233 | ``` 234 | При этом, если ```x``` нигде нет, то мы получим ошибку компиляции на этапе инстанцирования. 235 | 236 | ## Ещё немного type trait'ов 237 | 238 | Не все type traits можно реализовать самому, т.к. иногда нужны знания о том, 239 | как компилятор представляет информацию(например ```std::is_union```). 240 | 241 | ### is_constructible 242 | 243 | Следующие type_traits основываются на технике SFINAE. 244 | ```cpp 245 | template 246 | struct is_constructible { 247 | private: 248 | template ()...))> 250 | static true_type f(int); 251 | 252 | template 253 | static false_type f(...); 254 | 255 | public: 256 | static const bool type = decltype(f(0))::value; 257 | }; 258 | 259 | template 260 | bool is_constructible_v = is_constructible::value; 261 | ``` 262 | 263 | ### is_copy_constructible 264 | 265 | ```cpp 266 | template 267 | struct is_copy_constructible { 268 | private: 269 | template ()))> 271 | static true_type f(int); 272 | 273 | template 274 | static false_type f(...); 275 | 276 | public: 277 | static const bool type = decltype(f(0))::value; 278 | }; 279 | 280 | template 281 | bool is_copy_constructible_v = is_copy_constructible::value; 282 | ``` 283 | 284 | ### is_nothrow_move_constructible 285 | 286 | ```cpp 287 | template 288 | struct is_nothrow_move_constructible { 289 | private: 290 | template ()))>> 292 | static true_type f(int); 293 | 294 | template 295 | static false_type f(...); 296 | 297 | public: 298 | static const bool type = decltype(f(0))::value; 299 | }; 300 | 301 | template 302 | bool is_nothrow_move_constructible_v = is_nothrow_move_constructible::value; 303 | ``` 304 | -------------------------------------------------------------------------------- /cpp/allocators.md: -------------------------------------------------------------------------------- 1 | # Аллокаторы 2 | 3 | ### Понятие аллокатора 4 | 5 | Работать с памятью напрямую через операторы ```new, delete``` не принято. 6 | Принято выполнять все подобные операции через класс-прослойку под названием аллокатор. 7 | 8 | Что такое аллокатор? 9 | 10 | Аллокатор - это шаблонный класс, предназначенный для выделения и удаления памяти 11 | под объекты данного типа. 12 | 13 | Аллокатор должен представлять два метода: ```allocate, deallocate```. 14 | 15 | ```allocate``` принимает параметр ```n``` - кол-во штук объектов, 16 | на которые надо выделить память, 17 | а ```deallocate``` принимает указатель и по историческим причинам ещё параметр ```size_t``` 18 | (позже разберёмся, что за он). 19 | 20 | Стандартный аллокатор(```std::allocator```) - просто прослойка, чтобы напрямую не писать 21 | ```new```. 22 | 23 | В большинстве контейнеров в шаблоне вы могли видеть аллокатор как параметр, 24 | т.е. его можно подменить своей реализацией с указанными методами. 25 | 26 | Зачем это может быть нужно? 27 | 28 | Стандартный аллокатор не соптимизирован под вашу программу. 29 | Выделение памяти делается ОС, а ОС не знает, 30 | куски какого размера наперёд вы собираетесь запрашивать. 31 | Вы же, как разработчик, наперёд можете знать, какого вида запросы могут приходить, 32 | и потому можете выбрать наиболее оптимальную стратегию выделения памяти. 33 | И данную стратегию вы можете реализовать через аллокатор, подменив им стандартный. 34 | 35 | > Как пример нестадартного аллокатора можно привести аллокатор, 36 | > который заранее выделяет огромный кусок памяти, а потом исходя из каких-то соображений 37 | > отдаёт небольший кусочки памяти из этого большого пулла 38 | > (например отдавать из самого левого, подходящего по размеру, или из самого большого куска). 39 | 40 | Приведём примерную реализацию стандартного аллокатора: 41 | 42 | ```cpp 43 | namespace std { 44 | template 45 | struct allocator { 46 | T* allocate(size_t n) { 47 | return ::operator new(n * sizeof(T)); 48 | // вызываем глобальную функцию, 49 | // потому что не нужно вызывать констуктор 50 | } 51 | 52 | // по стандарту требуется, чтобы в deallocate передавалось 53 | // то же самое size_t n, что приходило и в allocate(как и T* p) 54 | // иначе ub 55 | void deallocate(T* p, size_t) { 56 | ::operator delete(p); // OC помнит, сколько байт выделила по конкретному указателю 57 | } 58 | } 59 | } // namespace std 60 | ``` 61 | 62 | В принципе это почти все нужные методы для аллокатора, 63 | но на данном этапе мы только выделяем память, 64 | а надо бы ещё вызывать конструктор. 65 | Т.к. контейнер напрямую не может работать с ```new```, 66 | то у аллокатора ещё может быть метод ```construct```: 67 | 68 | ```cpp 69 | template 70 | void construct(T* p, Args&& ...args) { 71 | new(p) T(std::forward(args)...); 72 | } 73 | ``` 74 | 75 | Т.е. на выделенной памяти мы создаём объект с помощью данного метода. 76 | Писать свой метод может понадобиться в случае, если мы например хотим 77 | логировать все подобные операции или запретить создавать объект с конкретными параметрами. 78 | 79 | Также стоит отметить, что реализация метода ```construct```(как и ```destroy```) необязательна. 80 | И если вы не напишете его, контейнер некоторым образом проверит, реализован ли данный метод, 81 | и если нет, вызовет стандартный. 82 | Этим занимается ```std::allocator_traits``` с помощью техники SFINAE. 83 | 84 | По аналогии есть метод ```destroy```: 85 | 86 | ```cpp 87 | void destroy(T* p) { 88 | p->~T(); 89 | } 90 | ``` 91 | 92 | > Заметки: 93 | > 94 | > 1. С точки зрения стандартного аллокатора все эти методы можно пометить ```const```. 95 | > 96 | > ```allocate``` не может быть ```noexcept```, т.к. конструктор может бросить исключение. 97 | > 98 | > ```deallocate``` конечно лучше ```noexcept``` не помечать, но можно сказать, что он 99 | > ```noexcept``` в зависимости от того, ```noexcept``` ли ```::operator delete(p)```. 100 | > 101 | > ```construct``` можно пометить ```noexcept(noexcept(T(args...)))```. 102 | > 103 | > ```destroy``` ```noexcept``` по умолчанию(деструкторы лучше именно так и помечать). 104 | > 105 | > 2. В аллокаторе должен быть определён ```value_type```: 106 | > 107 | > ```cpp 108 | > using value_type = T; 109 | > ``` 110 | > 111 | > Это нужно, чтобы пользователь аллокатора мог узнать, от чего данный аллокатор. 112 | > Множество других typedef'ов реализует ```std::allocator_traits```. 113 | 114 | ### allocator_traits 115 | 116 | Мы рассмотрели 4 метода аллокаторов, 2 из которых обязательны, а 2 нет. 117 | Однако с С++17 ```construct``` и ```destroy``` у стандартного аллокатора уже не реализованы, 118 | т.к. они реализованы на ещё одном уровне прослойки, которая занимается тем, чтобы 119 | дореализовывать методы за аллокатором, которые нереализованны в нём самом. 120 | 121 | Что такое ```traits``` в С++? Это такой класс, 122 | который содержит определение вещей для какого-то метатипа. 123 | Т.е. ```allocator traits``` - это такая "штука", которая определяет за аллокатор всё то, 124 | что он не доопределил. 125 | 126 | ```cpp 127 | // все методы статические 128 | template 129 | struct allocator_traits { 130 | // приведём реализацию allocate 131 | // стандартный аллокатор не меняется, но кастомный может, потому передаём по ссылке 132 | static Alloc::value_type* allocate(Alloc& alloc, size_t n) { 133 | return alloc.allocate(n); 134 | } 135 | // аналогично deallocate 136 | 137 | // для написания construct/destroy нужно использовать SFINAE 138 | } 139 | ``` 140 | 141 | > Давайте улучшим ```push_back```. 142 | > 143 | > Помним, что у вектора есть шаблонный параметр ```Alloc alloc``` 144 | > (а также нужные поля 145 | > ```size_t size, capacity; T* arr```. 146 | > 147 | > ```cpp 148 | > void push_back(const T& x) { 149 | > using traits = std::allocator_traits; 150 | > if (size == capacity) { 151 | > T* new_arr = traits::allocate(alloc, capacity <<= 1); 152 | > for (size_t i = 0; i < sz; ++i) { 153 | > // если не move, то будет копирование 154 | > traits::construct(alloc, new_arr + i, std::move_if_noexcept(arr[i])); 155 | > } 156 | > // не стоит делать всё в одном цикле, т.к. 157 | > // push_back даёт гарантию, что в случае, когда вылетит исключение, 158 | > // ничего не сломается, а если всё делать в одном цикле, мы не сможем 159 | > // вернуть уничтоженные объекты(если возвращать их обратно, 160 | > // то что, если опять исключение? 161 | > // а так мы просто вернёмся к ситуации до push_back) 162 | > for (size_t i = 0; i < sz; ++i) { 163 | > traits::destroy(alloc, arr + i); 164 | > } 165 | > traits::deallocate(alloc, arr, sz); 166 | > arr = new_arr; 167 | > } 168 | > traits::construct(alloc, arr + sz++, x); 169 | > } 170 | > ``` 171 | 172 | ### select_on_container_copy_construction 173 | 174 | Что делать с аллокатором, если мы хотим скопировать, например, вектор? 175 | 176 | В случае стандартного аллокатора проблем нет, т.к. он не хранить никаких полей, 177 | а все вызовы - просто обёртка над ```new```. 178 | Его конструктор копирования имеет пустое тело. 179 | 180 | В случае кастомного аллокатора копирование может быть нетривиальным. 181 | Например что делать, если аллокатор выделяет огромный пулл памяти заранее, 182 | а потом как-то выдаёт небольшими кусками? 183 | Как тут стоит поступить: выделять такой же пулл или просто давать один пулл обоим аллокаторам. 184 | И вот чтобы различать копирование аллокатора и контейнера на аллокаторе существует вот такая 185 | методология. Иногда мы не хотим копировать весь аллокатор, копируя контейнер, ведь может нам 186 | нужна та же сущность аллокатора, что бы на ней же выделять память. 187 | И вот чтобы решить в момент копирования контейнера, хотим ли мы копировать ещё и аллокатор, 188 | существует такой метод, который должен быть определён у аллокатора, который либо 189 | возвращает копию аллокатора, либо ссылку на изначальный. 190 | Т.е. в момент копирования вектора аллокатор инициализируется не ```alloc(other.alloc)```, а 191 | ```alloc(alloc.select_on_container_copy_construction())```. 192 | И стоит понимать, что аллокатор не обязан определять этот метод, тогда вызов делается так: 193 | 194 | ```cpp 195 | std::allocator_traits::select_on_container_copy_construction(alloc); 196 | ``` 197 | 198 | И если метод неопределён, то аллокатор будет просто скопирован. 199 | 200 | ### one more thing 201 | 202 | Предположим, что мы пишем свой ```std::list```, который хранит ```int```. 203 | И соответственно отдаём ему ```allocator```. 204 | Но ```list``` же не хранит числа, он хранит ноды, а аллокатор у нас создан для чисел. 205 | Что делать? 206 | 207 | На самом деле ```allocator``` может предоставлять ```typedef``` под названием ```rebind```. 208 | Т.е. имея аллокатор, мы можем захотеть получить точно такой же аллокатор, но для другого типа. 209 | И можем написать: 210 | 211 | ```cpp 212 | template 213 | using rebind::other = allocator; 214 | ``` 215 | 216 | Это нужно для того, когда кто-то пользуется вашим аллокатором, мог попросить 217 | ```allocator::rebind::other```. 218 | Опять же можно не определять, и ```allocator_traits``` это доопределит. 219 | 220 | ### Источники 221 | 222 | Можно почитать статьи/посмотреть доклады: 223 | 1. [Базовые концепции аллокаторов](https://habr.com/ru/post/590415/). 224 | 2. [Аллокаторы внутри](https://habr.com/ru/post/645137/). 225 | 3. CppCon 2015. Andrei Alexandrescu. 226 | [std::allocator](https://www.youtube.com/watch?v=LIb3L4vKZ7U). 227 | -------------------------------------------------------------------------------- /cpp/inheritance.md: -------------------------------------------------------------------------------- 1 | # Заметки про наследование 2 | 3 | ### Видимость и доступность 4 | 5 | При работе с переменными и функциями стоит различать понятия 6 | "видимость" и "доступность". 7 | Например когда мы помечаем некоторым модификатором _доступа_ 8 | переменную или функцию в классе, она не перестаёт быть видимой, 9 | однако меняет свою доступность. 10 | 11 | Давайте разберёмся, что при наследовании видимо(или нет), а что (не)доступно. 12 | 13 | Рассмотрим такую ситуацию: 14 | ```cpp 15 | struct A { 16 | void f(int); 17 | }; 18 | struct B: A { 19 | void f(double); 20 | }; 21 | ... 22 | B b; 23 | b.f(1); 24 | ``` 25 | Какая ```f``` вызовется? 26 | Вызовется из ```B```, хотя кажется, что есть другая, которая подходит лучше. 27 | А она не видна. 28 | ```f``` из ```A``` как раз доступна, но не видима, потому что 29 | ```f(double)``` перекрывает ```f(int)``` будто переменная из более локальной 30 | области видимости перекрывает переменную из более глобальной. 31 | Т.е. во время поиска имён рассматриваются сначала все имена в наследнике, 32 | а потом в родителе. 33 | 34 | Аналогично с полями. 35 | Если в обоих классах мы заведём поле ```int a;``` и напишем ```b.a = 1;```, 36 | мы будем иметь дело с ```a``` из класса ```B```. 37 | Хотя на самом деле класс будет хранить два разных поля с одинаковым именем 38 | ```a```, одно из них не будет видимым(но доступным). 39 | 40 | А что делать, если мы хотим обратиться к конкретному полю или методу из наследника? 41 | Надо явно указать область видимости: 42 | ```cpp 43 | b.A::f(1); // f(int) 44 | b.A::a = 2; 45 | ``` 46 | 47 | Вот этот "приём", когда мы явно указываем область видимости, называется 48 | *qualified id*, соответственно *unqualified id* - когда мы пишем через точку, 49 | не указывая область видимости. 50 | 51 | Теперь другой пример: 52 | ```cpp 53 | struct A { 54 | void f(int); 55 | int a; 56 | }; 57 | struct B: A { 58 | private: 59 | void f(double); 60 | int a; 61 | }; 62 | ... 63 | B b; 64 | b.f(1); 65 | ``` 66 | Тут мы получим ошибку компиляции в силу того, что компилятор выбирает из видимых 67 | имён наиболее подходящее, а уже потом, выбрав, смотрит, доступна ли эта функция. 68 | Точно так же, если бы речь шла не о наследовании, а просто в классе было 2 функции: 69 | одна от ```public int```, другая от ```private double```, - 70 | и в результате ```b.f(1.2)```, компилятор сначала выберет наиболее подходящую функцию, 71 | а уже потом поймёт, что она недоступна, и бросит ошибку компиляции. 72 | Т.е. он не будет выбирать менее подходящую, но более доступную. 73 | 74 | Получили, что компилятор всегда сначала выбирает, что вызывать(смотрит видимость), 75 | а уже потом проверяет, можно ли это вызвать(проверяет доступность). 76 | 77 | ### using в наследниках 78 | 79 | А что если мы хотим, чтобы функции из класса ```А``` из 1ого примера участвовали в перегрузке 80 | наравне с функциями из класса ```B```? 81 | 82 | Тут можно использовать ```using```, чтобы добавить функции и поля в текущую область видимости: 83 | ```cpp 84 | using A::f; 85 | ``` 86 | 87 | С ```C++11``` также можно делать ```using``` для конструкторов: 88 | ```cpp 89 | struct A { 90 | A(int); 91 | A(double); 92 | }; 93 | struct B: public A { 94 | using A::A; 95 | }; 96 | ``` 97 | Теперь мы умеем конструировать экземпляры ```B``` с помощью конструкторов ```A```, 98 | причём все поля наследника будут инициализированы дефолтным значением. 99 | > Стоит различать этот приём с делегирующими конструкторами. 100 | 101 | ### friend 102 | 103 | Так же наследники или родители могут объявлять друг друга ```friend```, но стоит понимать, 104 | что это не наследуется и всё ещё работает в одну сторону. 105 | 106 | ```cpp 107 | struct A { 108 | void f(int); 109 | }; 110 | struct B: private A { 111 | void f(double); 112 | }; 113 | struct C: B { 114 | void f() { 115 | B m; // так можно написать 116 | A g; // тут будет ошибка компиляции в силу того, что в контексте C 117 | // тип A означает C::A, но из-за такой цепочки наследования C::A 118 | // нам недоступно, потому что у C есть часть A, 119 | // которая является приватной с точки зрения C 120 | // если мы хотим создать объект A, надо явно указать, 121 | // что мы рассматриваем не часть класса C, а глобальный тип: 122 | ::A g; 123 | } 124 | }; 125 | ``` 126 | 127 | Однако если в ```B``` написать ```friend struct C```, то теперь в ```C``` можно будет написать 128 | ```A g```, т.к. ```B``` открыл видимости для всего, что есть в себе. 129 | Причём если написать ```friend struct C``` в ```A```, то это не поможет, потому что 130 | доступ перекрывает ```B```, а не ```A```. 131 | 132 | ### virtual, override 133 | 134 | У виртуальной и перегруженной функции сигнатуры должны совпадать **полностью**. 135 | ```cpp 136 | virtual void f() const; 137 | void f(); 138 | ``` 139 | Тут 2я функция не перегружает первую, потому что у них разные сигнатуры. 140 | Причём самое обидное, что компилятор никак на такое не поругается. 141 | 142 | В C++11 добавили ключевое слово ```override```, чтобы явно показать, что вы хотите 143 | переопределить виртуальную функцию. 144 | Причём можно не писать, но иначе компилятор подскажет, что, например, сигнатуры не совпадают. 145 | Очень удобно. 146 | 147 | Ещё есть слово ```final```. 148 | Такую функцию нельзя переопределить в наследниках. 149 | От ```final```класса больше нельзя будет отнаследоваться. 150 | 151 | ### Использование приватного члена из родителя 152 | 153 | ```cpp 154 | struct A { 155 | virtual void f() {} 156 | }; 157 | class B: A { 158 | private: 159 | void f() override {} 160 | }; 161 | ... 162 | B b; 163 | A& a = b; 164 | a.f(); 165 | ``` 166 | В данном случае будет вызвана версия из ```B```, несмотря на её приватность. 167 | Суть в том, что публичность/приватность - концепции compile-time, а выбор 168 | версии виртуальной функции происходит в runtime(пойти по указателю в виртуальную таблицу 169 | и выбрать нужную версию). 170 | 171 | ### virtual destructor 172 | 173 | Представим ситуацию: 174 | ```cpp 175 | struct base {}; 176 | struct der : base { 177 | int* p; 178 | 179 | def(int n) { 180 | p = new int(n); 181 | } 182 | 183 | ~def() { 184 | delete p; 185 | } 186 | }; 187 | ``` 188 | А теперь сделаем вот так: 189 | ```cpp 190 | base* pb = new der(5); 191 | delete pb; 192 | ``` 193 | Тут будет утечка памяти. 194 | Почему же? 195 | 196 | Ведь ```pb``` - указатель на ```base```, а что происходит, когда 197 | мы вызываем ```delete```? 198 | Мы вызываем деструктор у объекта, лежащего под указателем. 199 | А деструктор чего будет вызван, ведь ```pb``` - указатель на ```base```? 200 | И получается, что ```p``` не удаляется. 201 | 202 | Для решения такой проблемы нужно использовать виртуальный деструктор: 203 | ```cpp 204 | virtual ~base() {} 205 | ``` 206 | Как только в типе появляется хоть что-то виртуальное, 207 | тип сразу становится полиморфным. 208 | 209 | ### vtable 210 | 211 | Давайте рассмотрим пример: 212 | ```cpp 213 | struct A { 214 | void f() {} 215 | }; 216 | struct B { 217 | virtual void f() {} 218 | }; 219 | ``` 220 | Сколько байт будет занимать экземпляр первой структуры? 221 | Правильный ответ: 1(некоторый фиктивный байт). 222 | Потому что существует требование, чтобы у двух любых объектов 223 | отличались адреса, чего с весом в 0 байт не достичь. 224 | 225 | Сколько байт будет занимать экземпляр второй структуры? 226 | Внезапно: 8. 227 | Потому что внутри хранится указатель. 228 | Ведь внутри полиморфный объект хранит указатель на область памяти, 229 | где хранится информация о том, что это за тип. 230 | 231 | В случае, когда происходит вызов вида ```a.f()```, происходит 232 | прыжок к месту исполнения данной функции. 233 | 234 | Если ```b.f()```(где под ```b``` лежит некоторый наследник), сначала произойдёт 235 | прыжок в некоторое таблицу, где компилятор выяснит, какую версию функции 236 | ```f``` вызывать, а уже потом из того места прыгает 237 | к фактическому месту исполнения кода. 238 | 239 | Эта таблица, в которой для данного объекта, по какому адресу надо искать 240 | перегруженные функции, называется виртуальной таблицей. 241 | Такая таблица содержится для каждого типа в единственном экземпляре. 242 | 243 | ### Виртуальное наследование 244 | 245 | Предположим, что имеем следующую иерархию: 246 | ``` 247 | A 248 | / \ 249 | B C 250 | \ / 251 | D 252 | ``` 253 | В таком случае в ```D``` будем иметь два экземпляра класса ```A```. 254 | 255 | Однако такого можно избежать. 256 | 257 | С++ позволяет построить такую иерархию наследования, чтобы 258 | каждый базовый класс встречался только единожды. 259 | 260 | Если мы хотим, чтобы часть ```A``` не дублировалась, следует наследоваться 261 | вот так: 262 | ```cpp 263 | B : virtual A 264 | C : virtual A 265 | ``` 266 | Не очень понятно, почему одно и то же слово используется и для функций, 267 | и для методов, ведь из-за виртуального наследования тип полиморфным 268 | не становится. 269 | 270 | Что такое виртуальный предок? 271 | Виртуальный предок - это такой предок, который создаётся где-то отдельно, 272 | а ```B, C``` будут указывать на один и этот объект, т.е. в них 273 | будет храниться указатель на место, где лежит экземпляр ```A```. 274 | > Стоит понимать, что в стандарте по факту сказано, что 275 | > при виртуальном наследовании должен хранится один экземпляр 276 | > базового класса, а обращение к полям должно быть однозначным. 277 | > То, как это реализовано, остаётся на усмотрение компилятора. 278 | > Тут указан лишь возможный вариант реализации. 279 | 280 | Виртуальное наследование может быть полезно при использовании исключений: 281 | ```cpp 282 | struct ex1 : std::exception { 283 | const char* what() const; 284 | }; 285 | 286 | struct ex2 : std::exception { 287 | const char* what() const; 288 | }; 289 | 290 | struct ex3 : ex1, ex2 {}; 291 | 292 | int main() { 293 | try { throw_ex3(); } 294 | catch(std::exception const& e) { std::cout << e.what(); } 295 | catch(...) {} 296 | } 297 | ``` 298 | Т.к. у ```ex3``` несколько предков ```std::exception```, то компилятор 299 | не сможет понять, к какому именно нужно привести объект ```ex3```, 300 | потому это исключение будет отловлено в ```catch(...)```. 301 | При виртуальном наследовании ```ex1```, ```ex2``` от ```std::exception``` 302 | всё исправится. 303 | 304 | ### Inaccesible base class 305 | 306 | Имеем вот такую иерархию: 307 | ``` 308 | A(a) 309 | \ 310 | B(b) A(a) 311 | \ / 312 | C(c) 313 | ``` 314 | Тут получится, что мы никак не сможем получить поле, которое принадлежит 315 | ```A```, от которого наследуется ```C``` напрямую, т.к. если писать: 316 | ```c.a```, то получим CE из-за неоднозначности(ambiguous). 317 | Чтобы получить ```a``` из родителя ```B```, то можно написать ```c.B::a```, 318 | но к ```a``` из прямого родителя ```A``` нет никакого способа обратится 319 | (ведь ```c.A::a``` тоже порождает неоднозначность). 320 | 321 | Да, можно решить эту проблему с помощью указателей или 322 | ```reinterpret_cast```, но это больше похоже на нелегальные читы. 323 | -------------------------------------------------------------------------------- /cpp/move_semantics.md: -------------------------------------------------------------------------------- 1 | # Move-семантика 2 | 3 | ### Зачем это надо 4 | 5 | 1. Давайте рассмотри первую очевидную реализацию ```swap```: 6 | ```cpp 7 | template 8 | void swap(T& x, T& y) { 9 | T t = x; 10 | x = y; 11 | y = t; 12 | } 13 | ``` 14 | Очевидной проблемой является излишнее копирование(ведь в случае, если 15 | ```T``` - какой-то контейнер, то каждое присваивание тратит линейное время). 16 | 17 | Да, у большинства контейнеров существует метод ```swap```(```v.swap(w)```), 18 | но для каждого тяжеловесного контейнера писать такой метод как-то нерентабельно. 19 | 20 | А представьте, если мы работаем с кастомными BigInt(которые сравниваются за линию). 21 | И хотим отсортировать массив из таких элементов. 22 | Каждый такой ```swap``` будет занимать очень много времени. 23 | 24 | 2. Или давайте вспомним реализацию метода ```construct``` из аллокаторов: 25 | ```cpp 26 | template 27 | void construct(T* p, const Args&... args) { 28 | new(p) T(args...); 29 | } 30 | ``` 31 | И представим вот такую ситуацию: 32 | ```cpp 33 | vector> v; 34 | v.push_back(vector(1000000000)); 35 | ``` 36 | Опуская все подробности работы ```push_back```, мы придём к тому, что 37 | в реализации ```construct``` выше вызовется ```vector(const vector&)```, 38 | т.е. конструктор копирования. Очень неприятно! 39 | 40 | Да, конечно в таком случае можно использовать ```v.emplace_back(1000000000)```. 41 | Тогда число будет передано в качестве аргументов, и не будет лишнего создания вектора. 42 | 43 | Давайте представим, что у нас есть функция, возвращающая огромный вектор: 44 | ``` 45 | vector f() { 46 | return vector(1000000000); 47 | } 48 | ... 49 | v.push_back(f()); 50 | ``` 51 | В таком случае не будет разницы между ```push_back``` и ```emplace_back```. 52 | 53 | 3. И проблема с использованием ```construct```(писали вот так): 54 | ```cpp 55 | traits::construct(alloc, newarr + i, arr[i]); 56 | ``` 57 | И здесь очень хочется сделать что-то более умное, чтобы не копировать 58 | элемент. 59 | 60 | Понятно, что в случае ```swap``` хотелось бы не просто копировать, 61 | а перекладывать все поля одного объекта в другой. 62 | На примере вектора понятно, что проще переложить указатель на память, чем все элементы. 63 | 64 | ### Как надо писать, чтобы всё классно работало 65 | 66 | Вот это правильная реализация ```swap``` для современных плюсов: 67 | ```cpp 68 | template 69 | void swap(T& x, T& y) { 70 | T t = std::move(x); 71 | x = std::move(y); 72 | y = std::move(t); 73 | } 74 | ``` 75 | ```std::move``` просто перекладывает всё из одного объекта в другой(тот, из которого, 76 | скорее всего становится невалидным). 77 | Стоит понимать, что проблема 1 решена. 78 | Но в реализации ```construct``` нельзя просто написать ```T(std::move(args)...)```, 79 | ведь ```std::move``` инвалидирует объект, и каждый раз, когда мы клали бы что-то 80 | в конец вектора, это становилось бы уничтоженным. 81 | Т.е. писать ```std::move``` там нельзя, потому что будем ломать то, 82 | что не хотели. 83 | Но и не писать тоже нельзя: слишком долго будет работать. 84 | Отложим эту проблему. 85 | 86 | Давайте разберёмся, как работает то, что мы уже решили. 87 | 88 | ### Как же реализованы конструкторы, что всё так работает 89 | 90 | В C++11 появились новый вид конструкторов(move-конструктор). 91 | Это конструктор, который принимает в качестве параметров нечто, что 92 | возвращено после ```std::move```. 93 | Давайте напишем его на примере вектора: 94 | ```cpp 95 | vector(vector&& other) // пока какой-то непонятный тип 96 | : alloc(std::move(other.alloc)) 97 | , sz(other.sz) 98 | , cp(other.cp) 99 | , arr(other.arr) { 100 | other.arr = nullptr; 101 | } 102 | ``` 103 | 104 | Аналогично пишется move-оператор присваивания. 105 | 106 | ### Когда стоит использовать конструктор перемещения 107 | 108 | Сформулируем правило, когда должен вызываться 109 | конструктор копирования, а когда конструктор перемещения, потому что 110 | понятно, что в перемещение хотелось бы отправлять не только результат 111 | ```std::move```. 112 | 113 | Рассмотрим на примере BigInt. 114 | 115 | Понятно, что вот такое выражение: 116 | ```cpp 117 | BigInt(BigInt()); 118 | ``` 119 | надо бы мувать(вообще не совсем так, но чуть позже поправимся). 120 | 121 | Или вот такое: 122 | ```cpp 123 | BigInt(x + y); 124 | ``` 125 | Или такое: 126 | ```cpp 127 | x++; 128 | ``` 129 | Но не такое: 130 | ```cpp 131 | ++x; 132 | ``` 133 | Сформулируем правило: если выражение является lvalue, то вызовем конструктор копирования, 134 | если rvalue, то перемещения. 135 | Ровно это и делается. 136 | 137 | ### Что же такое rvalue/lvalue 138 | 139 | lvalue включает в себя: 140 | + identifier(если выражение это просто идентификатор(просто имя переменной), 141 | то это lvalue(```x;```)), строковый литерал; 142 | + если функция возвращает reference return type(```type&```); 143 | + built-in operators: ```prefix ++, prefix --, =, op=, unary *```; 144 | + cast to lvalue-reference. 145 | 146 | rvalue включает в себя: 147 | + любой литерал(кроме строкового); 148 | + если функция возвращает non-reference return type or rvalue-reference return type; 149 | + built-in operators: ```postfix ++, postfix --, +, -, /, *, %, <<, >>, &, |, ^, &&, ||, 150 | ==, <=, >=, <, >, !=, ~, !```; 151 | + cast to non-reference or rvalue-reference. 152 | 153 | Оператор запятая имеет такой тип value, какой имеет последний операнд. 154 | 155 | ```?:``` имеет такой, какой имеют оба возвращаемых типа, причём они должны быть одинаковыми, потому что 156 | иначе возможна была бы ситуация ```(cond ? x : 5) = y```. 157 | 158 | У операторов ```[]```, ```.```, ```->``` аналогично: к какому применяем, такое и получаем. 159 | 160 | Давайте поймём, что же такое ```std::move```. 161 | 162 | Название вводит в заблуждение: можно подумать, что она что-то перемещает, но на самом деле 163 | она просто говорит трактовать текущий объект как rvalue-reference. 164 | 165 | ### rvalue-references properties 166 | 167 | ``` 168 | int x = 5; 169 | int&& y; // нужно что-то сюда класть 170 | int&& y = x; // вот так тоже нельзя, потому что справа lvalue 171 | int&& y = std::move(x); 172 | int&& y = static_cast(x); // это два почти эквивалентных способа 173 | int&& z = y; // так нельзя написать, ведь y lvalue, а нельзя биндить lvalue к rvalue-ссылкам 174 | int& z = y; // так можно 175 | ``` 176 | 177 | ### std::move 178 | 179 | Давайте реализуем ```std::move```. 180 | 181 | Для начала непонятно, что она должна возвращать и принимать. 182 | 183 | Основная проблема в принимаемом значении. 184 | Дело в том, что ```std::move``` должны уметь принимать как lvalue, так и rvalue объекты, 185 | возвращая rvalue-ссылку. 186 | Если мы передадим ```const T&```, то нет, потому что мы не можем биндить lvalue-ссылки 187 | к временным объектам. 188 | Если ```T&``` - не сможем вызывать функцию от временных объектов. 189 | А если ```T&&``` - не сможем от невременных. 190 | 191 | Для решения проблемы немного изменены правила вывода типов для шаблонных аргументов с 192 | двойным амперсандом, т.к. ```T&&``` биндится как с rvalue, так и с lvalue. 193 | Такая ссылка называется universal reference(это неформальное понятие, не из стандарта). 194 | Т.е. в случае ```std::move(5)``` ```T = int```, ```type(x) = int&&```. 195 | Но если ```int y; std::move(y);```, существует правило, что в таком случае вместо ```T``` 196 | будет подставлен не просто тип, а тип с одиночным амперсандом. 197 | В этом случае происходит так называемое reference collapsing: 198 | если на один амперсанд навесить ещё два, останется лишь один. 199 | 200 | Из-за этого правила возникает 2 ситуации, когда у нас тип может быт с ссылкой и без. 201 | Но возвращать всегда нужно rvalue-ссылку. 202 | Так вот чтобы всегда это делать, нужно возвращать ```std::remove_reference_t&&```. 203 | ```cpp 204 | template 205 | std::remove_reference_t&& move(T&& x) { 206 | return static_cast&&>(x); 207 | } 208 | ``` 209 | 210 | ### perfect forwarding 211 | 212 | Мы уже решили проблему с ```std::swap```, но остались проблемы с ```push_back``` 213 | и неэффективным ```construct```. 214 | ```cpp 215 | template 216 | void construct(T* p, const Args&... args) { 217 | new(p) T(args...); // почему нельзя юзать std::move, уже говорили 218 | } 219 | ``` 220 | Теперь вместо ```const Args&``` можем написать ```Args&&```. 221 | 222 | И вот именно для такого места придуман этот костыль с универсальной ссылкой, т.к. 223 | в ```std::move``` мы ещё как-то могли бы обойтись, но тут мы теперь для каждого типа 224 | из ```Args``` можем сохранять информацию о том, что было передано: rvalue или lvalue 225 | (т.е. для каждого параметра мы отдельно можем понимать, стоит ли его мувать). 226 | 227 | Но нужно что-то ещё написать в ```T(args...)```. 228 | 229 | Для этого существует ещё одна функция ```std::forward```: она в зависимости от типа параметра 230 | либо кастует к rvalue, либо нет(в то время как ```std::move``` - это безусловный каст). 231 | 232 | Для чего это надо? Каждый раз, когда мы будем передавать эти параметры, 233 | необходимо сохранять категорию value переменной, ведь если этого не делать, 234 | то при ```T(args...)``` аргументы станут lvalue независимо от количества амперсандов. 235 | 236 | ```std::forward``` позволяет сохранить категорию value. 237 | 238 | Используется это вот так: 239 | ```cpp 240 | std::forward(args); 241 | ``` 242 | И тип в шаблоне либо имеет амперсанды, либо нет, в зависимости от чего происходит каст. 243 | 244 | Как же работает ```std::forward```? 245 | Возвращает он ```T&&```, но принимать такой же он не может, ведь придёт нам в функцию только 246 | lvalue. 247 | Надо действовать в зависимости от шаблонного аргумента: 248 | ```cpp 249 | template 250 | T&& forward(std::remove_reference_t& x) { 251 | return static_cast(x); 252 | } 253 | ``` 254 | Теперь решена проблема с ```construct```: 255 | ```cpp 256 | template 257 | void construct(T* p, Args&&... args) { 258 | new(p) T(std::forward(args)...); 259 | } 260 | ``` 261 | А ещё мы решили проблему с ```push_back```. 262 | Каким образом? 263 | Скорее всего существует две реализации ```push_back```: 264 | ``` 265 | void push_back(const T&); 266 | void push_back(T&&); 267 | ``` 268 | И стоит понимать, что во второй это rvalue-ссылка в чистом виде. 269 | Может показаться, что это универсальная, но она была бы универсальной, 270 | ведь универсальной ссылкой считается такая ссылка, что она имеет вид ```T&&```, 271 | и при этом ```T``` есть шаблонный параметр этой функции. 272 | Но тут это шаблонный параметр класса, он уже подставлен во время инстанцирования класса. 273 | Потому это по факту то же самое, что и конструктор копирования и конструктор перемещения. 274 | И отличаются они лишь тем, что в одном случае в конце мы пишем просто 275 | ```construct(alloc, newarr + i, x)```, 276 | а во втором случае - ```construct(alloc, newarr + i, std::move(x))```. 277 | 278 | > Конечно можно реализовать с помощью универсальной ссылки: 279 | > ``` 280 | > template 281 | > void push_back(U&&); 282 | > ``` 283 | > И функция будет вызываться как для lvalue, так и для rvalue. 284 | > Но тогда в конце функции нужно будет писать 285 | > ```construct(alloc, newarr + i, std::forward(x))```. 286 | 287 | Таким образом мы решили все поставленные проблемы. 288 | 289 | ### std::move_if_noexcept 290 | 291 | Вернёмся к такой строке: 292 | ``` 293 | construct(alloc, newarr + i, std::move(arr[i])); 294 | ``` 295 | Дело в том, что ```push_back```, как и другие контейнеры, старается поддержать безопасность 296 | относительно исключений. 297 | Конечно не всем функциям в контейнерах это удаётся, но ```push_back``` всё-таки 298 | реализован безопасно. 299 | 300 | Предположим, что во время реаллокации всех элементов при перекладывании из старого 301 | массива в новый move-конструктор на каком-то элементе бросил исключение. 302 | Но после ```std::move``` уже переложенная часть старого массива невалидна, 303 | а ещё не переложенная часть нового тоже не готова. 304 | Что в таком случае делать? 305 | Ведь перекладывать обратно мы не можем: а вдруг вылетит второе исключение? 306 | Потому в случае, когда move-конструктор бросает исключение, 307 | ```push_back``` становится небезопасным относительно исключений. 308 | 309 | В стандарте решили эту проблему вот так: вместо ```std::move``` в этом месте используется 310 | ```std::move_if_noexcept```. 311 | Что это такое? 312 | Эта функция возвращает rvalue-ссылку только в том случае, если move-конструктор 313 | является ```noexcept```, иначе возвращает lvalue-ссылку. 314 | Реализован он вот так: 315 | ```cpp 316 | using ret_type = std::conditional, 317 | std::remove_reference_t&&, 318 | std::remove_reference_t&>; 319 | template 320 | ret_type move_if_noexcept(T&& x) { 321 | return static_cast(x); 322 | } 323 | ``` 324 | 325 | ### Категории выражения 326 | 327 | На самом деле в C++11 существует не 2 категории выражений(lvalue, rvalue), а 5. 328 | ``` 329 | glvalue rvalue 330 | / \ / \ 331 | lvalue xvalue prvalue 332 | ``` 333 | glvalue - generalized lvalue. 334 | 335 | xvalue - expired value. Это либо ```static_cast```, 336 | либо возвращаемый тип функции, 337 | которая возвращает тип с двойным амперсандом. 338 | Т.е. это какой-то именованный объект, 339 | который вообще-то lvalue, но его решили трактовать, как rvalue. 340 | 341 | prvalue - rvalue. Это всё, что rvalue и не xvalue. 342 | По факту это временный объект, который только что создали на месте. 343 | 344 | Различия между ними несложные, но такая классификация несёт в себе несколько удобных моментов. 345 | Например когда мы пишем ```T(T())```, компилятор понимает, что можно не вызывать 346 | конструктор дважды, и соптимизирует это. 347 | В этом и различие двух видов: prvalue фактически может не иметь под собой никакой сущности, что 348 | позволяет иногда не создавать лишнее или как-то оптимизировать. 349 | в то время как с xvalue так нельзя. 350 | 351 | Это позволяет использовать такую вещь как copy elision. 352 | Компиляторы оптимизировали такие вещи и раньше, но начиная с C++17 такое строго документированно. 353 | 354 | copy elision - это предписание компилятору прям по стандарту оптимизировать выражения 355 | некоторого вида и не делать столько вызовов конструктора, сколько написано. 356 | Если это prvalue, и сразу понятно, что получится, то не надо создавать его. 357 | Это ещё работает когда пишем вот так: 358 | ```cpp 359 | T x = T(); 360 | ``` 361 | Но если бы мы написали 362 | ```cpp 363 | T x = std::move(T()); 364 | ``` 365 | мы получили бы xvalue, а xvalue обязывает создать объект, и вот тут мы получили бы 366 | дважды вызов конструктора, но один из них перемещающий. 367 | Это объясняет, почему иногда не надо писать ```std::move```, когда нам кажется, что надо. 368 | Т.е. иногда он вреден. 369 | Например ещё в такой ситуации: 370 | ```cpp 371 | T f() { 372 | T x; 373 | return x; 374 | } 375 | ``` 376 | Не стоит писать ```return std::move(x)```, ведь если не писать 377 | ```std::move```, то будет copy elision, 378 | что оптимальнее(объект создастся прям на месте). 379 | 380 | Также не стоит использовать ```std::move``` из константных объектов: 381 | ```cpp 382 | const string s = MakeString(); 383 | vector strs; 384 | strs.push_back(std::move(s)); // бесполезно 385 | ``` 386 | 387 | Ещё упомянем такой термин как temporary materialization - это когда 388 | prvalue кастуется к xvalue. 389 | Например в случае, когда у prvalue вызывается метод: 390 | ``` 391 | T().f(); 392 | ``` 393 | 394 | Ещё можно изучить 395 | [```std::make_move_iterator```](https://en.cppreference.com/w/cpp/iterator/make_move_iterator). 396 | 397 | ### reference_qualifiers 398 | 399 | По аналогии с вызовом от (не)константных возможно мы хотим вызывать различные 400 | версии функции в зависимости от их типа value. 401 | Тут нам помогут reference_qualifiers. 402 | ```cpp 403 | struct Data { 404 | Data(const std::string& s) : data(s) {} 405 | 406 | std::string get() & { 407 | return data; 408 | } 409 | 410 | std::string get() && { 411 | return std::move(data); 412 | } 413 | }; 414 | ``` 415 | При вызове ```x.get()``` будет осуществлён вызов первой версии, потому что это версия только для lvalue. 416 | При вызове чего-то вроде ```std::move(x).get()``` - второй(для rvalue). 417 | -------------------------------------------------------------------------------- /cpp/templates.md: -------------------------------------------------------------------------------- 1 | # templates 2 | 3 | ## Type deduction notes 4 | 5 | ### User-defined deduction guides(since C++17) 6 | 7 | В C++17 появилась возможность автовывода шаблонных типов, когда это возможно: 8 | ```cpp 9 | std::vector v{1, 2, 3}; 10 | ``` 11 | Рассмотрим пример: 12 | ```cpp 13 | template 14 | struct S { 15 | S(T x) {} 16 | }; 17 | ... 18 | S s("abs"); 19 | ``` 20 | Тут тип ```T``` будет выведен как ```const char*```, но можно попросить компилятор 21 | в таких случаях выводить его как ```std::string```. 22 | Для этого после класса пишем: 23 | ```cpp 24 | S(const char*) -> S; 25 | ``` 26 | Можно также писать шаблонные подсказки: 27 | ```cpp 28 | template 29 | S(const T&) -> S; 30 | ``` 31 | 32 | Или например следующий код: 33 | ```cpp 34 | template 35 | struct container { 36 | template 37 | container(Iter beg, Iter end); 38 | }; 39 | 40 | template 41 | container(Iter beg, Iter end) -> 42 | containter::value_type>; 43 | 44 | std::vector v; 45 | auto d = container(v.begin(), v.end()); // container 46 | ``` 47 | 48 | ### decltype 49 | 50 | Приоритет для ```decltype``` это точный тип параметра: 51 | ```cpp 52 | const int& x = 1; 53 | decltype(x) y = 1; // -> const int& y 54 | ``` 55 | А что же тут? 56 | ```cpp 57 | struct Point { int x, y; }; 58 | Point pp{1, 2}; 59 | const Point& p = pp; 60 | decltype(p.x) x = 0; // int x or const int& x? 61 | ``` 62 | Получим вот такой результат: 63 | ```cpp 64 | decltype(p.x) x = 0; // int x 65 | decltype((p.x)) x = 0; // const int& 66 | ``` 67 | Важный кейс: если в ```decltype(expr)``` оказывается, что ```expr``` - 68 | lvalue, то ```decltype(expr)``` добавляет lvalue reference 69 | к выведенному типу: 70 | ```cpp 71 | decltype((pp)) x = pp; // Point& x = pp; 72 | ``` 73 | или 74 | ```cpp 75 | int x[10]; 76 | x[5] = 4; // x[5] ведёт себя как ссылка 77 | decltype(x[5]) y = x[5]; // int& y = x[5]; 78 | y = 4; // изменяет x[5] 79 | ``` 80 | 81 | ### decltype(auto) 82 | 83 | Вывод типов является точным, но при этом выводится из всей правой части: 84 | ```cpp 85 | double x = 1.0; 86 | decltype(x) tmp = x; // вместо повторения x 87 | decltype(auto) tmp = x; // perfect! 88 | ``` 89 | Также стоит принимать во внимание: 90 | ```cpp 91 | decltype(auto) tmp = x; // double 92 | decltype(auto) tmp = (x); // double& 93 | ``` 94 | 95 | ### Проблема гетерогенного минимума 96 | 97 | Рассмотрим проблему примерно из 2012 года: мы не имеем ```auto``` 98 | для возвращаемого типа функций: 99 | ```cpp 100 | template 101 | auto /// Fail! 102 | doWork(const T& builder) { 103 | auto val = builder.makeObject(); 104 | // some code 105 | return val; 106 | } 107 | ``` 108 | Заменить ```auto``` на ```decltype(builder.makeObject())``` 109 | не увенчается успехом, т.к. в этом месте ещё не существует никакого 110 | ```builder```. На помощь приходит ```null pointer dereference```: 111 | ```cpp 112 | template 113 | decltype(((*T)(0))->makeObject()) // but pain... 114 | doWork(const T& builder) { 115 | auto val = builder.makeObject(); 116 | // some code 117 | return val; 118 | } 119 | ``` 120 | Т.к. выражение внутри ```decltype``` не вычисляется, всё будет корректно. 121 | 122 | Чтобы облегчить жизнь программистам был введён ```trailing```: 123 | ```cpp 124 | template 125 | auto doWork(const T& builder) -> 126 | decltype(builder.makeObject()) { 127 | auto val = builder.makeObject(); 128 | // some code 129 | return val; 130 | } 131 | ``` 132 | 133 | Теперь взглянем на следующий код: 134 | ```cpp 135 | template 136 | auto min(T x, S y) -> decltype(x < y ? x : y) { 137 | return x < y ? x : y; 138 | } 139 | ``` 140 | Т.к. тернарный оператор возвращает lvalue, то в случае 141 | совпадения типов возвращаемый тип будет ```type&```. 142 | Учитывая, что параметры - копии, получим dangling reference. 143 | 144 | Потому начиная с C++14 такая проблема решается просто: 145 | раз правила ```decltype``` не подходят, то подойдут 146 | правила для ```auto```, т.е. все cv-квалификаторы отбросятся. 147 | 148 | Хотя конечно и такое иногда ломается: 149 | ```cpp 150 | auto bad_sum(int i) { 151 | return (i > 2) ? bad_sum(i - 1) + i : i; 152 | } 153 | ``` 154 | Тут ```auto``` не сможет отработать, 155 | ведь до выведения типа он уже был использован. 156 | Ошибка так и будет звучать: use before deduction. 157 | Если поменять выражения в операторе местами, (по идее) 158 | всё должно скомпилироваться, т.к. станет понятно, 159 | какой возвращаемый тип; 160 | 161 | ### Более точный вывод для C++14 162 | 163 | Стоит помнить, что ```auto``` режет типы: 164 | ```cpp 165 | string f1(); 166 | string& f2(); 167 | 168 | auto d1() { return f1(); } // -> string d1(); 169 | auto d2() { return f2(); } // -> string d2(); 170 | ``` 171 | Тут можно использовать ```decltype(auto)```: 172 | ```cpp 173 | decltype(auto) d1() { return f1(); } // -> string d1() 174 | decltype(auto) d2() { return f2(); } // -> string& d2() 175 | ``` 176 | 177 | ### Исследование выведенных типов 178 | 179 | Существует оператор ```typeid(...)```, который для полиморфного типа 180 | скажет, какой это тип на самом деле. 181 | ```typeid(...)``` возвращает объект ```std::type_info```, который содержит 182 | информацию, что на самом деле лежит под данным именем. 183 | У него есть метод ```name()```, который вернёт строковое представление 184 | названия типа. 185 | Оно может не совпадать с тем именем, которое дали вы в программе, но тем не менее 186 | это какая-то полезная информация. 187 | 188 | Первый (самым плохой) вариант: 189 | ```cpp 190 | template 191 | void f(T* const& t) { 192 | std::cout << typeid(t).name() << std::endl; 193 | } 194 | int x = 1; 195 | f(&x); // на экране int* const 196 | ``` 197 | Получаем манглированное имя, которое можно обработать с помощью 198 | ```c++filt -t``` и получить результат. 199 | Минусы: код должен исполнится, часть информации обрезается. 200 | 201 | Используем линкер: 202 | ```cpp 203 | template 204 | void f(T* const& t); 205 | int x = 1; 206 | f(&x); // error: undefined reference to `void foo(int* const&)` 207 | ``` 208 | Однако это работает лишь для объектов с external linkage. 209 | А если хочется для локальной переменной? 210 | ```cpp 211 | template 212 | struct Type; 213 | template 214 | void f(T* const& t) { 215 | Type t1; // error: `Type t1` has incomplete type 216 | Type t2; // error: `Type t2` has incomplete type 217 | } 218 | ``` 219 | Раз мы хотим вызвать ошибку компиляции, какая разница, 220 | что мы подставим в шаблон :) 221 | 222 | Однако использовать такой способ для выяснения overkill. 223 | 224 | Приемлемым способ может быть решение с помощью ```boost```: 225 | ```cpp 226 | #include 227 | using boost::typeindex::type_id_with_cvr; 228 | std::cout << type_id_with_cvr().pretty_name(); 229 | ``` 230 | Эта функция не отбрасывает (что понятно по названию) cv-квалификаторы и ссылку, 231 | как это делает стандартный ```typeid```. 232 | 233 | И всё же, если не хочется тянуть ```boost```, то можно применить следующий способ: 234 | ```cpp 235 | template 236 | class Type {}; 237 | 238 | template 239 | void f(const T& t) { 240 | Type tt; 241 | std::cout << typeid(tt).name(); // c++filt -t 242 | } 243 | ``` 244 | Шаблон вынуждает ничего не резать. 245 | 246 | Если требуется только проверка типов на равенство, можно сделать так: 247 | ```cpp 248 | constexpr const int i = 0; 249 | constexpr int j = 0; 250 | using T = const int; 251 | using T = decltype(i); 252 | using T = decltype(j); 253 | ``` 254 | Если скомпилируется, то типы равны, иначе получаем redefenition error. 255 | 256 | ## templates notes 257 | 258 | ### Non-type template parameters(nntp) 259 | 260 | Всем известно, что в качестве шаблонов можно использовать также примитивные 261 | числовые типы: ```int```, ```size_t```, ```bool``` и т.д.(в противоположность 262 | ```double``` и ```float```). 263 | 264 | Но малоизвестен тот факт, что также можно использовать указатели: 265 | ```cpp 266 | template 267 | class MyClass {}; 268 | ``` 269 | Может возникнуть вопрос, где же взять указатель кроме ```nullptr``` в compile time. 270 | Например вот: 271 | ```cpp 272 | static const int values[] = {4, 8, 15, 16, 23, 42}; 273 | MyClass myClass; 274 | 275 | static char magic = '*'; 276 | MyClass myClass2; 277 | ``` 278 | Это может быть полезно, если вам нужно дать имя своему классу. 279 | 280 | Можно так же передать указатель на функцию: 281 | ```cpp 282 | template 283 | struct CBack { 284 | void use() { 285 | callback_(); 286 | } 287 | }; 288 | 289 | CBack<&foo> c; 290 | c.use(); 291 | ``` 292 | 293 | ### Template template parameters 294 | 295 | Можно задавать в шаблонах типы, которые сами являются шаблонными. 296 | На примере стека: 297 | ```cpp 298 | template