diff --git a/concurrency/vptr.md b/concurrency/vptr.md index 9a130c2..6d1744d 100644 --- a/concurrency/vptr.md +++ b/concurrency/vptr.md @@ -8,7 +8,7 @@ class Actor { public: virtual ~Actor() = default; - // мы опустим детали и обхекты + // мы опустим детали и объекты // для передачи сообщений между акторами // важно лишь что был метод run virtual void run() = 0; diff --git a/runtime/ownership_and_exceptions.md b/runtime/ownership_and_exceptions.md index 711ab7d..441aa89 100644 --- a/runtime/ownership_and_exceptions.md +++ b/runtime/ownership_and_exceptions.md @@ -49,7 +49,7 @@ if (auto item_handle = lru_get(cache, key)) { } ``` -Код восхтитителен тем, что явно выполняет целых два обращения к кэшу: на вставку и на проверку. Но ведь можно было бы ограничиться только одной вставкой, если `lru_insert` может предоставить необходимую информацию об успехе... Может ли дело быть в этом? Нет ли в этом сервисе случайно гонок, которые могут вклиниться между вставкой и проверкой? Но меня уверили, что процесс однопоточный. +Код восхитителен тем, что явно выполняет целых два обращения к кэшу: на вставку и на проверку. Но ведь можно было бы ограничиться только одной вставкой, если `lru_insert` может предоставить необходимую информацию об успехе... Может ли дело быть в этом? Нет ли в этом сервисе случайно гонок, которые могут вклиниться между вставкой и проверкой? Но меня уверили, что процесс однопоточный. Наверное, стоит углубиться в функцию `lru_insert`. Ее написали 10 лет назад и больше не трогали. Ее протестировали. Она надежна. Как я могу в ней сомневаться? diff --git a/runtime/static_initialization_order_fiasco.md b/runtime/static_initialization_order_fiasco.md index 64a3465..7ef1286 100644 --- a/runtime/static_initialization_order_fiasco.md +++ b/runtime/static_initialization_order_fiasco.md @@ -7,7 +7,7 @@ (в разные .c/.cpp файлы). Они могут компилироваться параллельно. Скорость сборки повышается. И все было бы хорошо. -Но только в одном «модуле» появляется глобальная переменная, используемая в другом модуле, начинаются проблемы. И проблемы не только от того, что глобальные переменные в принципе признак не самого удачного дизайна. Проблема в том, что связи между модулями нет (заголовочные файлы ничего не связывают). И после объединения модулей код с инициализацией глобальной переменной может оказаться ПОСЛЕ кода с использованием. +Но как только в одном «модуле» появляется глобальная переменная, используемая в другом модуле, начинаются проблемы. И проблемы не только от того, что глобальные переменные в принципе признак не самого удачного дизайна. Проблема в том, что связи между модулями нет (заголовочные файлы ничего не связывают). И после объединения модулей код с инициализацией глобальной переменной может оказаться ПОСЛЕ кода с использованием. Стандарты C и С++ гарантируют, что глобальные переменные будут сконструированы в порядке их объявления внутри единицы трансляции. А между единицами трансляции — неопределен. И вместе с порядком неопределено и поведение программы. diff --git a/runtime/uninitialized.md b/runtime/uninitialized.md index a66693e..6c1bd6b 100644 --- a/runtime/uninitialized.md +++ b/runtime/uninitialized.md @@ -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)). -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)* поведения. И ошибка чтения неинициализированной переменной считается теперь ошибочным, а не неопределенным поведением. На практике же это значит, что вы все также успешно отстрелите себе ногу, но компиляторам рекомендуется выдать диагностику и запрещается делать оптимизации — какой-то определенный мусор должен быть успешно прочитан, а оптимизации кода до и после такого чтения не должны делать никаких предположений об этом мусорном значении. @@ -120,7 +120,7 @@ int answer = 0; Специальные функции, например, `std::make_unique_for_overwrite` мы не рассматриваем. Функции выделения сырой памяти: `*alloc` тоже. Хотя напомнить, что писать `(T*)malloc(N)` в ожидании инициализированной памяти нельзя. -В боле общем случае, если верно, что `is_trivially_constructible == true`, то +В более общем случае, если верно, что `is_trivially_constructible == true`, то 1. `T x;` 2. `T x[N];` diff --git a/standard_lib/enable_if_void_t.md b/standard_lib/enable_if_void_t.md index 687b54e..5489dd5 100644 --- a/standard_lib/enable_if_void_t.md +++ b/standard_lib/enable_if_void_t.md @@ -81,7 +81,7 @@ fun(T) { // 2 fun(X{}); // несмотря на то что значение std::is_same_v всегда истинно, X::Outer не существует. И SFINAE сработает не из-за значения предиката, а из-за его аргументов. ``` -И тут начинатся первая неприятность: +И тут начинается первая неприятность: `std::enable_if` против `std::enable_if_t`. ```C++ diff --git a/standard_lib/function_pass_and_address_restriction.md b/standard_lib/function_pass_and_address_restriction.md index d819a46..fd325f6 100644 --- a/standard_lib/function_pass_and_address_restriction.md +++ b/standard_lib/function_pass_and_address_restriction.md @@ -92,7 +92,7 @@ int main() { Да, почти все функции стандартной библиотеки C++17, после инстанциирования шаблонов, все-таки оказываются нормальными функциями и потому у нас уж сколько лет все работает. -C функциями стандартной библиотеки C все, конечно, хуже -- они могут быть макросами, и черт его знает от чего вы на самом деле взяли адресс в таком случае. +C функциями стандартной библиотеки C все, конечно, хуже -- они могут быть макросами, и черт его знает от чего вы на самом деле взяли адрес в таком случае. С C++20 (вдохновленные ranges [Эрика Ниблера](https://github.com/ericniebler)) новые (а также потенциально старые после перехода std на модули) функции внезапно могут оказаться *ниблоидами*. Глобальными объектами с определенным `operator()` -- так что они могут выглядеть и крякать как старые добрые функции, но таковыми не быть. И если вы использовали `С-style` каст вместо громоздкого `static_cast`, то вас могут ждать интересные результаты: @@ -161,7 +161,7 @@ int main() { Плохая новость: замечательно не будет, поэтому придется писать код -Проблема решится оборачиваением вызова к std функции в вашу функцию или в лямбду. +Проблема решится оборачиванием вызова к std функции в вашу функцию или в лямбду. ```C++ int main() { @@ -253,7 +253,7 @@ int main() { integrate(LAMBDA_WRAP(std::sqrt)); } ``` -Что поледать, раздутие кода -- известный результат [мономорфизации](https://en.wikipedia.org/wiki/Monomorphization) шаблонов/generic функций. +Что поделать, раздутие кода -- известный результат [мономорфизации](https://en.wikipedia.org/wiki/Monomorphization) шаблонов/generic функций. Переиспользуйте лямбду, и будет лучше: diff --git a/standard_lib/shared_from_this.md b/standard_lib/shared_from_this.md index 14af675..e3e2335 100644 --- a/standard_lib/shared_from_this.md +++ b/standard_lib/shared_from_this.md @@ -1,8 +1,8 @@ # 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_); } - void private: std::shared_ptr listener_; std::weak_ptr