Много typos и небольших правок (#124)

* Update comparison_operator_rewrite.md: typos

* Update default_default_constructor.md: typos

Не уверен насчёт добавления `2D` к Point. По смыслу, вроде бы, нужно

* Update shared_from_this.md

* Update function_pass_and_address_restriction.md

* Update enable_if_void_t.md

* Update static_initialization_order_fiasco.md

* Update uninitialized.md

* Update ownership_and_exceptions.md

* Update vptr.md
This commit is contained in:
Aleksandr
2025-04-29 02:00:42 +03:00
committed by GitHub
parent b38daaf0ff
commit 034dc9504b
9 changed files with 23 additions and 24 deletions

View File

@@ -8,7 +8,7 @@
class Actor { class Actor {
public: public:
virtual ~Actor() = default; virtual ~Actor() = default;
// мы опустим детали и обхекты // мы опустим детали и объекты
// для передачи сообщений между акторами // для передачи сообщений между акторами
// важно лишь что был метод run // важно лишь что был метод run
virtual void run() = 0; virtual void run() = 0;

View File

@@ -49,7 +49,7 @@ if (auto item_handle = lru_get(cache, key)) {
} }
``` ```
Код восхтитителен тем, что явно выполняет целых два обращения к кэшу: на вставку и на проверку. Но ведь можно было бы ограничиться только одной вставкой, если `lru_insert` может предоставить необходимую информацию об успехе... Может ли дело быть в этом? Нет ли в этом сервисе случайно гонок, которые могут вклиниться между вставкой и проверкой? Но меня уверили, что процесс однопоточный. Код восхитителен тем, что явно выполняет целых два обращения к кэшу: на вставку и на проверку. Но ведь можно было бы ограничиться только одной вставкой, если `lru_insert` может предоставить необходимую информацию об успехе... Может ли дело быть в этом? Нет ли в этом сервисе случайно гонок, которые могут вклиниться между вставкой и проверкой? Но меня уверили, что процесс однопоточный.
Наверное, стоит углубиться в функцию `lru_insert`. Ее написали 10 лет назад и больше не трогали. Ее протестировали. Она надежна. Как я могу в ней сомневаться? Наверное, стоит углубиться в функцию `lru_insert`. Ее написали 10 лет назад и больше не трогали. Ее протестировали. Она надежна. Как я могу в ней сомневаться?

View File

@@ -7,7 +7,7 @@
(в разные .c/.cpp файлы). Они могут компилироваться параллельно. (в разные .c/.cpp файлы). Они могут компилироваться параллельно.
Скорость сборки повышается. И все было бы хорошо. Скорость сборки повышается. И все было бы хорошо.
Но только в одном «модуле» появляется глобальная переменная, используемая в другом модуле, начинаются проблемы. И проблемы не только от того, что глобальные переменные в принципе признак не самого удачного дизайна. Проблема в том, что связи между модулями нет (заголовочные файлы ничего не связывают). И после объединения модулей код с инициализацией глобальной переменной может оказаться ПОСЛЕ кода с использованием. Но как только в одном «модуле» появляется глобальная переменная, используемая в другом модуле, начинаются проблемы. И проблемы не только от того, что глобальные переменные в принципе признак не самого удачного дизайна. Проблема в том, что связи между модулями нет (заголовочные файлы ничего не связывают). И после объединения модулей код с инициализацией глобальной переменной может оказаться ПОСЛЕ кода с использованием.
Стандарты C и С++ гарантируют, что глобальные переменные будут сконструированы в порядке их объявления внутри единицы трансляции. А между единицами трансляции — неопределен. И вместе с порядком неопределено и поведение программы. Стандарты C и С++ гарантируют, что глобальные переменные будут сконструированы в порядке их объявления внутри единицы трансляции. А между единицами трансляции — неопределен. И вместе с порядком неопределено и поведение программы.

View File

@@ -4,7 +4,7 @@
Новые современные языки программирования обычно запрещают использование неинициализированных переменных. Переменные либо всегда инициализируются значением по умолчанию (например, в [Go](https://golang.org/ref/spec#The_zero_value)). Либо попытка чтения из неинициализированной переменной дает ошибку компиляции (в [Kotlin](https://pl.kotl.in/PoVXtB7AB) или в [Rust](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=261f92c8ca39b10c1ac565e4f8a1e28a)). Новые современные языки программирования обычно запрещают использование неинициализированных переменных. Переменные либо всегда инициализируются значением по умолчанию (например, в [Go](https://golang.org/ref/spec#The_zero_value)). Либо попытка чтения из неинициализированной переменной дает ошибку компиляции (в [Kotlin](https://pl.kotl.in/PoVXtB7AB) или в [Rust](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=261f92c8ca39b10c1ac565e4f8a1e28a)).
C и C++ — старые языки. В них можно легко и просто объявить переменную, а инициализировать ее как-нибудь потом. Или забыть иницаилизировать вовсе. Но в отличие от совсем низкоуровневого ассемблера, в котором читать из неинициализированной переменной никто не запрещает — ну получите вы свои мусорные байтики и ладно — в C/C++ (а также в Rust, см [MaybeUninit](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html)) это влечет за собой неопределенное поведение. C и C++ — старые языки. В них можно легко и просто объявить переменную, а инициализировать ее как-нибудь потом. Или забыть инициализировать вовсе. Но в отличие от совсем низкоуровневого ассемблера, в котором читать из неинициализированной переменной никто не запрещает — ну получите вы свои мусорные байтики и ладно — в C/C++ (а также в Rust, см [MaybeUninit](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html)) это влечет за собой неопределенное поведение.
Но время не стоит на месте даже для C++ и в последних версиях стандарта (C++26 и новее) всё же произошли некоторые изменения: стандарт вводит новое понятие *ошибочного* *(erroneous)* поведения. И ошибка чтения неинициализированной переменной считается теперь ошибочным, а не неопределенным поведением. На практике же это значит, что вы все также успешно отстрелите себе ногу, но компиляторам рекомендуется выдать диагностику и запрещается делать оптимизации — какой-то определенный мусор должен быть успешно прочитан, а оптимизации кода до и после такого чтения не должны делать никаких предположений об этом мусорном значении. Но время не стоит на месте даже для C++ и в последних версиях стандарта (C++26 и новее) всё же произошли некоторые изменения: стандарт вводит новое понятие *ошибочного* *(erroneous)* поведения. И ошибка чтения неинициализированной переменной считается теперь ошибочным, а не неопределенным поведением. На практике же это значит, что вы все также успешно отстрелите себе ногу, но компиляторам рекомендуется выдать диагностику и запрещается делать оптимизации — какой-то определенный мусор должен быть успешно прочитан, а оптимизации кода до и после такого чтения не должны делать никаких предположений об этом мусорном значении.
@@ -120,7 +120,7 @@ int answer = 0;
Специальные функции, например, `std::make_unique_for_overwrite` мы не рассматриваем. Функции выделения сырой памяти: `*alloc` тоже. Хотя напомнить, что писать `(T*)malloc(N)` в ожидании инициализированной памяти нельзя. Специальные функции, например, `std::make_unique_for_overwrite` мы не рассматриваем. Функции выделения сырой памяти: `*alloc` тоже. Хотя напомнить, что писать `(T*)malloc(N)` в ожидании инициализированной памяти нельзя.
В боле общем случае, если верно, что `is_trivially_constructible<T> == true`, то В более общем случае, если верно, что `is_trivially_constructible<T> == true`, то
1. `T x;` 1. `T x;`
2. `T x[N];` 2. `T x[N];`

View File

@@ -81,7 +81,7 @@ fun(T) { // 2
fun(X{}); // несмотря на то что значение std::is_same_v<T, T> всегда истинно, X::Outer не существует. И SFINAE сработает не из-за значения предиката, а из-за его аргументов. fun(X{}); // несмотря на то что значение std::is_same_v<T, T> всегда истинно, X::Outer не существует. И SFINAE сработает не из-за значения предиката, а из-за его аргументов.
``` ```
И тут начинатся первая неприятность: И тут начинается первая неприятность:
`std::enable_if` против `std::enable_if_t`. `std::enable_if` против `std::enable_if_t`.
```C++ ```C++

View File

@@ -92,7 +92,7 @@ int main() {
Да, почти все функции стандартной библиотеки C++17, после инстанциирования шаблонов, все-таки оказываются нормальными функциями и потому у нас уж сколько лет все работает. Да, почти все функции стандартной библиотеки C++17, после инстанциирования шаблонов, все-таки оказываются нормальными функциями и потому у нас уж сколько лет все работает.
C функциями стандартной библиотеки C все, конечно, хуже -- они могут быть макросами, и черт его знает от чего вы на самом деле взяли адресс в таком случае. C функциями стандартной библиотеки C все, конечно, хуже -- они могут быть макросами, и черт его знает от чего вы на самом деле взяли адрес в таком случае.
С C++20 (вдохновленные ranges [Эрика Ниблера](https://github.com/ericniebler)) новые (а также потенциально старые после перехода std на модули) функции внезапно могут оказаться *ниблоидами*. Глобальными объектами с определенным `operator()` -- так что они могут выглядеть и крякать как старые добрые функции, но таковыми не быть. С C++20 (вдохновленные ranges [Эрика Ниблера](https://github.com/ericniebler)) новые (а также потенциально старые после перехода std на модули) функции внезапно могут оказаться *ниблоидами*. Глобальными объектами с определенным `operator()` -- так что они могут выглядеть и крякать как старые добрые функции, но таковыми не быть.
И если вы использовали `С-style` каст вместо громоздкого `static_cast`, то вас могут ждать интересные результаты: И если вы использовали `С-style` каст вместо громоздкого `static_cast`, то вас могут ждать интересные результаты:
@@ -161,7 +161,7 @@ int main() {
Плохая новость: замечательно не будет, поэтому придется писать код Плохая новость: замечательно не будет, поэтому придется писать код
Проблема решится оборачиваением вызова к std функции в вашу функцию или в лямбду. Проблема решится оборачиванием вызова к std функции в вашу функцию или в лямбду.
```C++ ```C++
int main() { int main() {
@@ -253,7 +253,7 @@ int main() {
integrate(LAMBDA_WRAP(std::sqrt)); integrate(LAMBDA_WRAP(std::sqrt));
} }
``` ```
Что поледать, раздутие кода -- известный результат [мономорфизации](https://en.wikipedia.org/wiki/Monomorphization) шаблонов/generic функций. Что поделать, раздутие кода -- известный результат [мономорфизации](https://en.wikipedia.org/wiki/Monomorphization) шаблонов/generic функций.
Переиспользуйте лямбду, и будет лучше: Переиспользуйте лямбду, и будет лучше:

View File

@@ -1,8 +1,8 @@
# std::shared_from_this # std::shared_from_this
Обсуждая особенности [`std::make_shared`](shared_ptr_constructor.md), я упоминул, что иногда крайне необходимо убедиться, что объекты вашего класса всегда создаются только в куче и управляются с помощью умного указателя. Вот сейчас будет еще один такой случай. Обсуждая особенности [`std::make_shared`](shared_ptr_constructor.md), я упомянул, что иногда крайне необходимо убедиться, что объекты вашего класса всегда создаются только в куче и управляются с помощью умного указателя. Вот сейчас будет еще один такой случай.
Вы разрабатываете графический интерфейс и, как это было принято лет 20 назад, решили что все доллжно быть объектно-ориентированно и красиво. Компонентики. Виджеты. Всех мы будем по требованию создавать, управлять умными shared и weak указателями, чтоб не очень сильно задумываться о владении — очень стандартный подход, между прочим. Rust библиотеки для GUI, например, часто [критикуют](https://www.warp.dev/blog/why-is-building-a-ui-in-rust-so-hard) за чудовищную сложность именно из-за владений. Вы разрабатываете графический интерфейс и, как это было принято лет 20 назад, решили что все должно быть объектно-ориентированно и красиво. Компонентики. Виджеты. Всех мы будем по требованию создавать, управлять умными shared и weak указателями, чтоб не очень сильно задумываться о владении — очень стандартный подход, между прочим. Rust библиотеки для GUI, например, часто [критикуют](https://www.warp.dev/blog/why-is-building-a-ui-in-rust-so-hard) за чудовищную сложность именно из-за владений.
И так у вас появились некоторые базовые типы: И так у вас появились некоторые базовые типы:
@@ -107,7 +107,6 @@ public:
listener_->notify(EventType::Clicked, self_); listener_->notify(EventType::Clicked, self_);
} }
void
private: private:
std::shared_ptr<EventListener> listener_; std::shared_ptr<EventListener> listener_;
std::weak_ptr<Button> self_; std::weak_ptr<Button> self_;
@@ -127,7 +126,7 @@ int main() {
Некрасиво, но работает... Некрасиво, но работает...
Но С++11 предлагает нам решенине получше! `std::enable_shared_from_this` Но С++11 предлагает нам решение получше! `std::enable_shared_from_this`
Который позволит нам автоматически добавить такое же self-поле и позаботится о его правильное заполнении Который позволит нам автоматически добавить такое же self-поле и позаботится о его правильное заполнении
при конструировании `shared_ptr`. при конструировании `shared_ptr`.
@@ -281,7 +280,7 @@ event received
widget valid widget valid
``` ```
Из конструктора мы отправили в Listener нулевой указатель... Ну а чего еще мы хотели?! Ведь работа конструктора еще не завершена. А как мы видели из ручной имитации shared_from_this, внутренний weak_ptr инициализируеься только после полного создания объекта. Так что все работает правильно. Хоть и неожиданно. Из конструктора мы отправили в Listener нулевой указатель... Ну а чего еще мы хотели?! Ведь работа конструктора еще не завершена. А как мы видели из ручной имитации shared_from_this, внутренний weak_ptr инициализируется только после полного создания объекта. Так что все работает правильно. Хоть и неожиданно.
Так что если хотите посылать сообщения из конструктора таким образом, то желательно перехотеть. И сделать статический фабричный метод вместо конструктора. Из него уже можно будет посылать уведомления сколько угодно. Так что если хотите посылать сообщения из конструктора таким образом, то желательно перехотеть. И сделать статический фабричный метод вместо конструктора. Из него уже можно будет посылать уведомления сколько угодно.
@@ -321,7 +320,7 @@ event received
widget valid widget valid
``` ```
Осталось пойти и сделать конструтор приватным с помощью привантного-тэга. Ведь иначе кто-нибудь обязательно создаст кнопку на стэке. От чего либо наполучает нулевых указателей Осталось пойти и сделать конструтор приватным с помощью приватного-тэга. Ведь иначе кто-нибудь обязательно создаст кнопку на стэке. От чего либо наполучает нулевых указателей
```C++ ```C++
int main() { int main() {

View File

@@ -32,9 +32,9 @@ c автоматической реализацией (через `=default`).
Добавим заголовок и все будет [работать](https://godbolt.org/z/5E4csxK6b). Здорово? Конечно же! Добавим заголовок и все будет [работать](https://godbolt.org/z/5E4csxK6b). Здорово? Конечно же!
Но есть кое-что еще. Но есть кое-что еще.
Не все типы можно или осмысленно упорядочивать. Иногда достаточно только равенства. Не все типы можно осмысленно упорядочивать. Иногда достаточно только равенства.
В C++20 можно определить `operator ==` и `operator !=` будет выведен автоматически. В C++20 можно определить `operator ==` и `operator !=` будет выведен автоматически.
Более того, реализация через `= defalt` также [работает](https://godbolt.org/z/Mc9YEsGWK). Более того, реализация через `= default` также [работает](https://godbolt.org/z/Mc9YEsGWK).
```C++ ```C++
struct Pair { struct Pair {
@@ -70,7 +70,7 @@ struct String {
bool operator==(const StringView &sv) const { bool operator==(const StringView &sv) const {
// а тут меняем порядок местами, ведь у StringView уже есть operator == // а тут меняем порядок местами, ведь у StringView уже есть operator ==
// overload resolution наедет его по первому аргументу // overload resolution наедет его по первому аргументу
// а ко второму (*this: String) можно применить неявное приведенине типа. Все отлично! // а ко второму (*this: String) можно применить неявное приведение типа. Все отлично!
return sv == *this; return sv == *this;
} }
}; };
@@ -102,7 +102,7 @@ Program terminated with signal: SIGSEGV
С++20 позаботился о разработчиках. И теперь им не нужно выдумывать странные перестановки аргументов местами чтоб писать поменьше перегрузок операторов сравнения. С++20 позаботился о разработчиках. И теперь им не нужно выдумывать странные перестановки аргументов местами чтоб писать поменьше перегрузок операторов сравнения.
C++20 ввел правила переписывания [всех операторов сравнения](https://en.cppreference.com/w/cpp/language/overload_resolution#Call_to_an_overloaded_operator), так что компиляторы выполнят перестановку за вас. Даже если в ней нет надобности. И разумеется веселые и находчивые разработчики старых кодовых баз получат бесконечную рекурсию. А c ней и неопределенное поведение. И SIGSEGV от переполнения стека, если повезет. C++20 ввел правила переписывания [всех операторов сравнения](https://en.cppreference.com/w/cpp/language/overload_resolution#Call_to_an_overloaded_operator), так что компиляторы выполнят перестановку за вас. Даже если в ней нет надобности. И разумеется веселые и находчивые разработчики старых кодовых баз получат бесконечную рекурсию. А c ней и неопределенное поведение. И SIGSEGV от переполнения стека, если повезет.
Эти изменения в правила поиска перегрузок для операций сравнения имели и другие побочные эффекты. Их постарались исправить в предложении [P2468R2 The Equality Operator You Are Looking For](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2468r2.html#code-patterns-which-fail-at-runtime). Он принято и реализовано в C++23. Эти изменения в правила поиска перегрузок для операций сравнения имели и другие побочные эффекты. Их постарались исправить в предложении [P2468R2 The Equality Operator You Are Looking For](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2468r2.html#code-patterns-which-fail-at-runtime). Оно принято и реализовано в C++23.
Но внезапную рекурсию все равно не убрали! Полагайтесь на предупреждения компилятора. Но внезапную рекурсию все равно не убрали! Полагайтесь на предупреждения компилятора.

View File

@@ -1,6 +1,6 @@
# Конструктор по умолчанию и = default # Конструктор по умолчанию и = default
Гайдлайны по современному C++ всячески намекают, а иногда напрямую советуют: [следуюйте "правилу нуля" (rule of zero)](https://en.cppreference.com/w/cpp/language/rule_of_three) для ваших классов, и структур и будет вам счастье! Используйте инициализацторы по умолчанию! C++20 улучшил поддержку структур-аггрегатов, так что не надо писать вручную конструкторы там где это не надо... Но legacy код существут, его затратно переписывать... А также существуют legacy разработчики, которые застряли в C++98... Гайдлайны по современному C++ всячески намекают, а иногда напрямую советуют: [следуйте "правилу нуля" (rule of zero)](https://en.cppreference.com/w/cpp/language/rule_of_three) для ваших классов, и структур и будет вам счастье! Используйте инициализаторы по умолчанию! C++20 улучшил поддержку структур-аггрегатов, так что не надо писать вручную конструкторы там где это не надо... Но legacy код существует, его затратно переписывать... А также существуют legacy разработчики, которые застряли в C++98...
Так что в старых кодовых базах можно встретить что-нибудь такое: Так что в старых кодовых базах можно встретить что-нибудь такое:
@@ -12,7 +12,7 @@ public:
Point2D(int _x, int _y); Point2D(int _x, int _y);
// Раз добавили какой-то конструктор, // Раз добавили какой-то конструктор,
// нужно добавить и конструктор по умолчанию // нужно добавить и конструктор по умолчанию
Point(); Point2D();
int x; int x;
int y; int y;
@@ -24,7 +24,7 @@ public:
// Point.cpp // Point.cpp
Point2D::Point2D(int _x, int _y) : x {_x}, y {_y} {} Point2D::Point2D(int _x, int _y) : x {_x}, y {_y} {}
// И даже такие! // И даже такие!
Point::Point2D() = default; Point2D::Point2D() = default;
``` ```
Делать так в современном C++ крайне не рекомендуется. Не только из-за обилия бессмысленного бойлерплейта, но и из-за риска получить неинициализированные поля и неопределенное поведение вместе с ними. Делать так в современном C++ крайне не рекомендуется. Не только из-за обилия бессмысленного бойлерплейта, но и из-за риска получить неинициализированные поля и неопределенное поведение вместе с ними.