Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/09_allocations_optimizations.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
— Я хочу. И вставлю в отдельный файл.

## Аллокация памяти на Linux.
На Linux страницы памяти выделяются через [`mmap`](http://man7.org/linux/man-pages/man2/mmap.2.html), а освобождают через `munmap`. Можно заметить интересный эффект: если мы просто делаем выделение памяти кучу раз, это будет относительно быстро, а когда мы начнём к выделенной памяти обращаться, ты мы зависнем надолго. Почему так? Точнее на вопрос «почему» ответ простой — потому что **ОС даёт нам память только тогда, когда мы её используем**, а вот «зачем» — интересный вопрос. Дело в том, что обычно у нас нет свободной оперативной памяти, она вся либо отдана другим программам, либо используется как дисковый кэш. Поэтому когда кто-то просит память, вам придётся сбрасывать дисковый кэш, либо выгружать кого-то в swap-файлы. А если вдруг ваша программа будет использовать выделенную память как-то потом (или не будет вообще), то давать ей память сразу невыгодно, поэтому ОС даёт её при обращении и маленькими кусочками (ведь используете вы маленькими кусочками, а `mmap` выделяет сразу кучу памяти). Кстати, **в `mmap` есть специальный флаг (`MAP_POPULATE`), который заставляет выделять память сразу**.
На Linux страницы памяти выделяются через [`mmap`](http://man7.org/linux/man-pages/man2/mmap.2.html), а освобождают через `munmap`. Можно заметить интересный эффект: если мы просто делаем выделение памяти кучу раз, это будет относительно быстро, а когда мы начнём к выделенной памяти обращаться, то мы зависнем надолго. Почему так? Точнее на вопрос «почему» ответ простой — потому что **ОС даёт нам память только тогда, когда мы её используем**, а вот «зачем» — интересный вопрос. Дело в том, что обычно у нас нет свободной оперативной памяти, она вся либо отдана другим программам, либо используется как дисковый кэш. Поэтому когда кто-то просит память, вам придётся сбрасывать дисковый кэш, либо выгружать кого-то в swap-файлы. А если вдруг ваша программа будет использовать выделенную память как-то потом (или не будет вообще), то давать ей память сразу невыгодно, поэтому ОС даёт её при обращении и маленькими кусочками (ведь используете вы маленькими кусочками, а `mmap` выделяет сразу кучу памяти). Кстати, **в `mmap` есть специальный флаг (`MAP_POPULATE`), который заставляет выделять память сразу**.

При этом, если сделать так, то работать это будет быстрее, чем если выделить память обычным образом, а потом к ней пообращаться, потому что с флагом не будет происходить множество переходов между userspace'ос и ОС при каждом обращении. Хорошо, а знаете ли вы, что **в `mmap` больше всего времени занимает обнуление выделенной страницы**. Зачем? А вдруг там кто-то криптографию оставил. Кто-то, кого вы только что убили, например. Но вообще под это тоже есть специальный флажок `MAP_UNINITIALIZED` (но только для анонимных страниц), который был создан специально для миниатюрных устройств. **Работает `MAP_UNINITIALIZED`, правда, только в том случае, если ОС была собрана специальным образом**. Если, кстати, делать `mmap` без флага `MAP_POPULATE`, то ещё больше времени, чем на зануление, будет тратиться на передачу обращения между ОС и userspace'ом.

Как подобное ленивое выделение работает с точки зрения страничной адресации? Вот так: при запросе выделения памяти через `mmap` ОС не сразу обращается к процессору, а помечает у себя страницы как "*заказанные*". Затем, когда происходит обращение к памяти, получаем ошибку *page fault*, ОС проверяет, если страница выделена, что она мапит её в физическую память, иначе это ошибка.

Как этим можно пользоваться? Типовое использование — это не выделение одной страницы, а сразу порции памяти, которая разбивается на мелкие куски и выдается `malloc`'ом. Когда они выдаются в программе, они мапаются в физическую память. Ещё мапить сразу в физическую память не очень полезно, так как память, не принадлежащая программой, используется ОС, например, как дисковый кэш.
Как этим можно пользоваться? Типовое использование — это выделение не одной страницы, а сразу порции памяти, которая разбивается на мелкие куски и выдается `malloc`'ом. Когда они выдаются в программе, они мапаются в физическую память. Ещё мапить сразу в физическую память не очень полезно, так как память, не принадлежащая программе, используется ОС, например, как дисковый кэш.

Зачем нам это знать? Это полезно, если мы что-то бенчмаркаем и выделяем большой массив, первый прогон какого-нибудь алгоритма может быть дольше остальных из-за
того, что он сначала не помаплен в память.
Expand Down
1 change: 1 addition & 0 deletions src/10_libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ int main(){
}
```
```c++
// five
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

five.cpp, тогда уж

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

#include <iostream>

int sum(int a, int b);
Expand Down
10 changes: 5 additions & 5 deletions src/14_templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ void baz(int*) {}

```c++
int main() {
foo(nullptr);
baz(nullptr);
}
```
Давайте подумаем, работает ли это, если мы включим `ENABLE_TEMPLATE`. А вот не работает, потому что непонятно, чему равно `T`. А вот с перегрузкой всё работает (выбирается перегрузка). Почему это так работает, хочется спросить?
Expand All @@ -177,9 +177,9 @@ int main() {

Подробнее про *Template argument deduction* на [cppreference](https://en.cppreference.com/w/cpp/language/template_argument_deduction).

Поэтому когда мы подставляем `nullptr`, то из него нельзя понять, на какой тип он указывает, поэтому *deduction* провалится, и мы получаем ошибку. Если же есть `void foo(int*)`, то выбирается он, как единственный подходящий.
Поэтому когда мы подставляем `nullptr`, то из него нельзя понять, на какой тип он указывает, поэтому *deduction* провалится, и мы получаем ошибку. Если же есть `void baz(int*)`, то выбирается он, как единственный подходящий.

Кстати, можно немного изменить работу с перегрузками. Можно вызывать функции не как `foo(...)`, а как `foo<>(...)`. В таком случае вы явно отбросите всё, что не является шаблоном, а значит выбирать будете только из специализаций.
Кстати, можно немного изменить работу с перегрузками. Можно вызывать функции не как `baz(...)`, а как `baz<>(...)`. В таком случае вы явно отбросите всё, что не является шаблоном, а значит выбирать будете только из специализаций.


## Non-type template parameter.
Expand Down Expand Up @@ -439,7 +439,7 @@ int main(){
return 0;
}
```
Без *main.cpp* компилируется, так как у `a` не вызывался деструктор, поэтому он не инстанцировался. С *main.cpp* компилятор генерирует деструктор, который вызывает деструкторы всех членов класса, а там `unigue_ptr<object>`, у которого при компиляции будет инстанцироваться деструктор. В `unique_ptr` есть специальная проверка, что если удаляется incomplete type (а у нас `object` именно таковой), то это ошибка.\
Без *main.cpp* компилируется, так как у `a` не вызывался деструктор, поэтому он не инстанцировался. С *main.cpp* компилятор генерирует деструктор, который вызывает деструкторы всех членов класса, а там `unique_ptr<object>`, у которого при компиляции будет инстанцироваться деструктор. В `unique_ptr` есть специальная проверка, что если удаляется incomplete type (а у нас `object` именно таковой), то это ошибка.\
Как решить проблему? Сделать объявление деструктора в *mytype.h*, а определить его там, где `object` — complete тип (то есть в *mytype.cpp*).

Ещё пример:
Expand Down Expand Up @@ -470,7 +470,7 @@ struct derived : base<derived> {

В предыдущем примере тот же самый эффект: так как `derived` шаблонный, то он не инстанцируется сразу, но когда мы инстанцируем `derived`, то он создаётся как incomplete (complete он станет после подстановки базовых классов), происходит подстановка base и получаем ошибку.

В конексте обсуждённого выше может быть интересно прочитать про идиому [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern).
В контексте обсуждённого выше может быть интересно прочитать про идиому [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern).


### Явное инстанцирование шаблонов.
Expand Down
13 changes: 13 additions & 0 deletions src/16_namespaces_using_adl.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,13 +296,26 @@ int main() {

Немного best practices о том, как надо делать `swap`:
```c++
namespace my_lib {
struct big_integer {};
void swap(big_integer&, big_integer&) {// ... //}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

таких комментариев в языке нет, лучше /* и */

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}

template <class T>
void foo(T a, T b) {
// ...
using std::swap;
swap(a, b);
// ...
}

int main() {
my_lib::big_integer a1, b1;
foo(a1, b1); // выбирается my_lib::swap

int a2, b2;
foo(a2, b2); // выбирается std::swap
}
```
Теперь у нас получается шаблонный `std::swap` и, возможно, есть не-шаблонный ADL.
- Если есть ADL, выбирается он, потому что из шаблонного и не-шаблонного выбирается второй.
Expand Down
2 changes: 1 addition & 1 deletion src/17_move_rvalue.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ struct person {
}
```

Так мы получим нужно поведение за исключением того, что иногда move-конструктор вызовется лишний раз (когда делаем копию и вызываем move). Обычно это оптимизируется и не влияет на производительность, но при необходимости можно сделать разные перегрузки.
Так мы получим нужное поведение за исключением того, что иногда move-конструктор вызовется лишний раз (когда делаем копию и вызываем move). Обычно это оптимизируется и не влияет на производительность, но при необходимости можно сделать разные перегрузки.

**Не стоит использовать move там, где он не нужен**:

Expand Down
42 changes: 42 additions & 0 deletions src/20_perfect_forwarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,45 @@ void write(T0 const& arg0, Ts const& ...args) {
write(args...);
}
```

## Fold Expressions

В **C++17** появилась фича **Fold Expressions**, позволяющая сделать свёртку по элементам пака относительно бинарного оператора.

Мотивирующий пример: попробуем научиться проверять, есть ли тип (T) в паке. Так как пак в C++ имеет вид lazy array (То есть мы имеем право получить только Head и Tail), очевидна следующая реализация:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

имена типов и других идентификаторов лучше заключать в `

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

часть предложения в скобках не должна начинаться с большой буквы

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

вроде fixed

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не знаю, является ли "C++26", но если является напишите

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"lazy array" это какой-то распространённый в таком контексте термин? я впервые слышу

давай или ссылку, где он применяется, либо не будем его упоминать

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ну здесь имеется в виду lazy sequence, но я лучше это просто уберу

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

перед сложным примером, уходящим в сторону метапрограммирования, полезно показать какой-нибудь более простой и типичный пример свёртки

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавил пример с sum


```c++
template <typename Type, typename... Pack>
struct have_type;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

может, has_type?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


template <typename Type, typename Head, typename... Tail>
struct have_type<Type, Head, Tail...> {
static constexpr bool value = std::is_same_v<Type, Head> ||
have_type<Type, Tail...>::value;
// ^^^^^^^^^^^^^^^^^^^^^^^^ инстанс нового шаблона
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

комментарий стоит индентировать

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это не инстанс нового шаблона, это инстанцирование новой специализации всё того же шаблона

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я не очень понимаю, что значит идентировать, но со "специализацией" согласен

};

template <typename Type, typename Head>
struct have_type<Type, Head> {
static constexpr bool value = std::is_same_v<Type, Head>;
};
```

Чем плоха такая реализация? Предположим в паке N элементов, тогда компилятор будет проводить N инстансов шаблона `have_type`. Как можно это пофиксить? Вспомним, что `...` ставится там, где перечисляются элементы. В Fold expressions это и используется. Тогда предыдущий пример можно переписать так:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto про идентификаторы

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


```c++
template <typename Type, typename... Pack>
struct have_type;

template <typename Type, typename... Types>
struct have_type<Type, Types...> {
static constexpr bool value = std::is_same_v<Type, Types> || ...;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это не скомпилируется по нескольким причинам

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

};
```

Чем это хорошо?
- Нет рекурсии.
- Быстрее compile time, потому что меньше инстансов и у компилятора есть возможность делать оптимизации.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

английский ну совсем на ровном месте, давай заменим на "время компиляции"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не совсем понял, о каких оптимизациях речь

Copy link
Copy Markdown
Author

@Bokceris1 Bokceris1 Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я здесь немного ошибся с примером в метапрограммировании (хотя здесь тоже должны быть оптимизации, потому что проиходит свёртка булов). Здесь всё-таки имеются в виду вообще оптимизации fold-ов, даже вне метапроги. Насчёт оптимизаций: я вообще думал, что компилятор умеет делать что-то типо свёртки констант в компайл тайме (Например свернуть sum(1, 2, 3, 4) в return 10), но почему-то когда я начал это на godbolt писать, то оно не воспроизвелось. Есть оптимизация булов https://godbolt.org/z/nbKcof5qf вот тут видно, что компилятор может соптимизировать вывод false после первого встреченного false, конечно он делает нечто подобное и в случае когда вызывается функция two, но он её не инлайнит, поэтому может быть больше джампов по программе например. Да и пример может быть сложнее. +у компилятора есть пространство для векторизации(то есть, допустим сложить несколько чиселок за 1 инструкцию). В общем случае, всё, что сказано выше заключено во фразе "Нет рекурсии", но я считаю важным написать про оптимизации

Copy link
Copy Markdown
Author

@Bokceris1 Bokceris1 Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я засомневался, что пример с оптимизацией булов действительно подходит, поэтому вот пример когда работает constant folding с делением https://godbolt.org/z/MYqYcnzG6 (с -O2)

- Легче читается и меньше кода.

Подробнее про Fold expressions на [cppreference](https://en.cppreference.com/w/cpp/language/fold.html).
2 changes: 1 addition & 1 deletion src/21_decltype_auto_nullptr.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ auto f(bool flag) { // COMPILE ERROR
```c++
struct nullptr_t {
template <typename T>
opeartor T*() const {
operator T*() const {
return 0;
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/22_lambdas_type_erasure.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ struct less {
// можно сделать класс со static-функцией, но это не даёт выигрыша
// так как ABI устроен так, что пустые структуры можно не копировать

int foo (vector& v) {
std::sort(v.begin(), v.end(), less<int>());
std::sort(v.begin(), v.end(), &int_less);
void foo (vector& v) {
std::sort(v.begin(), v.end(), less<int>()); // 1.
std::sort(v.begin(), v.end(), &int_less); // 2.
}
```

Expand Down Expand Up @@ -375,7 +375,7 @@ auto bind(F f, Args... args) {
}
```

На смом деле, там [немного сложнее](https://en.cppreference.com/w/cpp/utility/functional/bind): можно закреплять конкретные аргументы, а остальные принимать при вызове.
На самом деле, там [немного сложнее](https://en.cppreference.com/w/cpp/utility/functional/bind): можно закреплять конкретные аргументы, а остальные принимать при вызове.

### Рекурсивный вызов лямбд

Expand Down