fix typos and lang syntax highlight

This commit is contained in:
Gleb Koveshnikov
2022-03-20 12:49:46 +03:00
parent 5fb6b7db19
commit 3e841ea40c
12 changed files with 36 additions and 36 deletions

View File

@@ -8,7 +8,7 @@
Сравнение вещественных чисел — излюбленная головная боль. Сравнение вещественных чисел — излюбленная головная боль.
Выражение `x == y` фактически является кривым побитовым сравнением для чисел с плавающей точкой, по особенному работающее со случаями `-0.0` и `+0.0`, и `NaN`. Выражение `x == y` фактически является кривым побитовым сравнением для чисел с плавающей точкой, по особенному работающее со случаями `-0.0` и `+0.0`, и `NaN`.
О существовании этого и `!=` операторов для вещественных чисел стоит забыть и никогда не вспонимать. О существовании этого и `!=` операторов для вещественных чисел стоит забыть и никогда не вспоминать.
Для побитового сравнения нужно использовать `memcmp`. Для побитового сравнения нужно использовать `memcmp`.
Для сравнения чисел — приближенные варианты вида `std::abs(x - y) < EPS`, где `EPS` — какое-то абсолютное или вычисляемое на основе `x` и `y` значение. А также различные манипуляции с [`ULP`](https://en.wikipedia.org/wiki/Unit_in_the_last_place) сравниваемых чисел. Для сравнения чисел — приближенные варианты вида `std::abs(x - y) < EPS`, где `EPS` — какое-то абсолютное или вычисляемое на основе `x` и `y` значение. А также различные манипуляции с [`ULP`](https://en.wikipedia.org/wiki/Unit_in_the_last_place) сравниваемых чисел.

View File

@@ -32,7 +32,7 @@ auto d = x / y;
## К чему это приводит? ## К чему это приводит?
1. К ошибкам в логике: 1. К ошибкам в логике:
Неявные преобразования вовлекаются в любую операцию. Вы выполняете сравнение знакового и беззнакового числа и забыли явно привести типы? Готовтесь к тому, что `-1 < 1` может [вернуть](https://godbolt.org/z/sqvrasjE4) `false`: Неявные преобразования вовлекаются в любую операцию. Вы выполняете сравнение знакового и беззнакового числа и забыли явно привести типы? Готовьтесь к тому, что `-1 < 1` может [вернуть](https://godbolt.org/z/sqvrasjE4) `false`:
```C++ ```C++
std::vector<int> v = {1}; std::vector<int> v = {1};
auto idx = -1; auto idx = -1;

View File

@@ -31,9 +31,9 @@ int main() {
В этом коде нет неопределенного поведение (по крайней мере на используемых входных данных). Но есть неявное приведение типов, делающее результат неожиданным. В этом коде нет неопределенного поведение (по крайней мере на используемых входных данных). Но есть неявное приведение типов, делающее результат неожиданным.
1. Тип возвращаемого значения `std::accumulate` определяется третьим аргументом. В данном случае это целочисленный знаковый ноль — тип по умолчанию для всех числовых литералов. 1. Тип возвращаемого значения `std::accumulate` определяется третьим аргументом. В данном случае это целочисленный знаковый ноль — тип по умолчанию для всех числовых литералов.
2. Тип возвращаемого значения операции деления определяется наибольшим из участвующих типов аргументов, а также правилами [integer promotion](https://wiki.sei.cmu.edu/confluence/display/c/INT02-C.+Understand+integer+conversion+rules). В примере тип левого арумента — `int`, а правого — `size_t` — достаточно широкое беззнакое целое, Более широкое чем `int`. Потому, по правилам integer promotion, результатом будет `size_t` 2. Тип возвращаемого значения операции деления определяется наибольшим из участвующих типов аргументов, а также правилами [integer promotion](https://wiki.sei.cmu.edu/confluence/display/c/INT02-C.+Understand+integer+conversion+rules). В примере тип левого аргумента — `int`, а правого — `size_t` — достаточно широкое беззнаковое целое, Более широкое чем `int`. Потому, по правилам integer promotion, результатом будет `size_t`
3. `-3` неявно преобразуется к типу `size_t` — такое преобразование вполне определено. Результатом будет беззнаковое число `2^N - 3`. 3. `-3` неявно преобразуется к типу `size_t` — такое преобразование вполне определено. Результатом будет беззнаковое число `2^N - 3`.
4. Далее будет проиведено деление беззнаковых числел. `(2^N - 3) / 3`. Старший бит результата окажется нулевым. 4. Далее будет произведено деление беззнаковых чисел. `(2^N - 3) / 3`. Старший бит результата окажется нулевым.
5. Возвращаемым типом функции `average` объявлен `int`. Так что нужно выполнить еще одно неявное преобразование. 5. Возвращаемым типом функции `average` объявлен `int`. Так что нужно выполнить еще одно неявное преобразование.
6. В общем случае преобразование unsigned -> signed определяется реализацией (implementation defined). 6. В общем случае преобразование unsigned -> signed определяется реализацией (implementation defined).
1. Если размеры типов `int` и `size_t` одинаковые, то, поскольку старший бит нулевой, положительное число укладывается в допустимый диапазон значений для типа `int` — стандарт гарантирует, что никаких проблем нет 1. Если размеры типов `int` и `size_t` одинаковые, то, поскольку старший бит нулевой, положительное число укладывается в допустимый диапазон значений для типа `int` — стандарт гарантирует, что никаких проблем нет
@@ -118,7 +118,7 @@ void g(int&& v) {
// Неявное преобразование int& к int&& запрещено // Неявное преобразование int& к int&& запрещено
// int&& x = 5; // int&& x = 5;
// int&& y = x; // не компилируеся! // int&& y = x; // не компилируется!
// Таким образом перегрузка f(int&&) не может быть использована // Таким образом перегрузка f(int&&) не может быть использована
@@ -157,15 +157,15 @@ int main() {
} }
``` ```
Той же самой цепочкой преоразований [получим](https://godbolt.org/z/bncnPj) в выводе `bool 1`. Той же самой цепочкой преобразований [получим](https://godbolt.org/z/bncnPj) в выводе `bool 1`.
Разве что последний шаг не нужен. Разве что последний шаг не нужен.
--- ---
Обязателько включайте предупреждения компилятора обо всех неявных преобразованиях. Очень желательно трактовать их как ошибки. Обязательно включайте предупреждения компилятора обо всех неявных преобразованиях. Очень желательно трактовать их как ошибки.
Не привносите невные преобразования для своих типов — всегда помечайте однопараметрические конструкторы как `explicit`. Не привносите неявные преобразования для своих типов — всегда помечайте однопараметрические конструкторы как `explicit`.
Если перегружаете операторы приведения (`operator T()`) для своих типов — также делайте их `explicit`. Если перегружаете операторы приведения (`operator T()`) для своих типов — также делайте их `explicit`.

View File

@@ -29,7 +29,7 @@ if (x > 0 && a > 0 && x + a <= 0) {
} }
``` ```
Но, увы, это неопреденной поведение. И компилятор имеет полное право [выкинуть](https://godbolt.org/z/dhs83T) такую проверку. Но, увы, это неопределенное поведение. И компилятор имеет полное право [выкинуть](https://godbolt.org/z/dhs83T) такую проверку.
Искусственный пример может быть недостаточно убедительным, так что обратим внимание на следущую, вполне серьезную, функцию вычисления полиномиального хэша строки: Искусственный пример может быть недостаточно убедительным, так что обратим внимание на следущую, вполне серьезную, функцию вычисления полиномиального хэша строки:
```C++ ```C++
@@ -171,9 +171,9 @@ ErrorOrInteger<I> mod(I a, std::type_identity_t<I> b) {
Однако, если ваша программа только и делает, что ожидает и выполняет IO операции, то траты в два раза большего числа тактов на сложение или умножение никто и не заметит. Да и язык C++ для таких программ чаще всего не лучший выбор. Однако, если ваша программа только и делает, что ожидает и выполняет IO операции, то траты в два раза большего числа тактов на сложение или умножение никто и не заметит. Да и язык C++ для таких программ чаще всего не лучший выбор.
Итак, если вы работаете только лишь с беззнаковыми числами (`unsigned`), то с неопределенным поведением при переполнеии никаких проблем нет — все определено как вычисления по модулю `2^N` (N — количество бит для выбранного типа чисел). Итак, если вы работаете только лишь с беззнаковыми числами (`unsigned`), то с неопределенным поведением при переполнении никаких проблем нет — все определено как вычисления по модулю `2^N` (N — количество бит для выбранного типа чисел).
Если же вы работаете со знаковыми числами, либо используйте безопасные обертки, сообщающие каким-либо образом об ошибках. Либо выводите ограничения на входные данные програмы целиком таким образом, чтобы переполнения не возникало, и не забывайте эти ограничения проверять. Все просто, да? Если же вы работаете со знаковыми числами, либо используйте безопасные обертки, сообщающие каким-либо образом об ошибках. Либо выводите ограничения на входные данные программы целиком таким образом, чтобы переполнения не возникало, и не забывайте эти ограничения проверять. Все просто, да?
Для выведения ограничений вам помогут отладочные `assert` с правильными проверками переполнения, которые нужно написать. Или включение `ubsan` (_undefined behavior sanitizer_) при сборке компиляторами `clang` или `gcc`. Для выведения ограничений вам помогут отладочные `assert` с правильными проверками переполнения, которые нужно написать. Или включение `ubsan` (_undefined behavior sanitizer_) при сборке компиляторами `clang` или `gcc`.
А также тестовые `constexpr` вычисления. А также тестовые `constexpr` вычисления.

View File

@@ -7,7 +7,7 @@
Но проблема в том, что этот спецификатор не заставляет компиляторы проверять, Но проблема в том, что этот спецификатор не заставляет компиляторы проверять,
что функция действительно не бросает исключений. что функция действительно не бросает исключений.
Если мы пометим фунцию как `noexcept`, а она возьмет да кинет исключение, Если мы пометим функцию как `noexcept`, а она возьмет да кинет исключение,
произойдет что-то странное, заканчивающееся внезапным `std::terminate`. произойдет что-то странное, заканчивающееся внезапным `std::terminate`.
Так, например, неожиданно [перестанут работать](https://godbolt.org/z/E9c9Ya) `try-catch` блоки. Так, например, неожиданно [перестанут работать](https://godbolt.org/z/E9c9Ya) `try-catch` блоки.
@@ -43,7 +43,7 @@ void throw_smth() {
- `= 0` для объявления чисто виртуальных методов - `= 0` для объявления чисто виртуальных методов
- новый `requires` имеет два значения, порождая странные конструкции `requires(requires(...))` - новый `requires` имеет два значения, порождая странные конструкции `requires(requires(...))`
- `auto` и для автовывода, и для переключения на trailing return type - `auto` и для автовывода, и для переключения на trailing return type
- `decltype`, у которого разный смысл при примении к переменной и к выражению - `decltype`, у которого разный смысл при применении к переменной и к выражению
- и, конечно, `noexcept` — точно также два значения как у `requires`. - и, конечно, `noexcept` — точно также два значения как у `requires`.
Есть спецификатор `noexcept(condition)`. И просто `noexcept` — синтаксический сахар Есть спецификатор `noexcept(condition)`. И просто `noexcept` — синтаксический сахар

View File

@@ -7,7 +7,7 @@
Некоторые имена запрещены самим стандартом C/C++. Некоторые — стандартами POSIX. Некоторые — платформоспецифическими библиотеками. В последнем случая обычно вам ничего не грозит, пока библиотека не подключена. Некоторые имена запрещены самим стандартом C/C++. Некоторые — стандартами POSIX. Некоторые — платформоспецифическими библиотеками. В последнем случая обычно вам ничего не грозит, пока библиотека не подключена.
Так в глобальной области видимости нельзя использовать имена функций из библиотеки C. Ни в C, ни в C++! Так в глобальной области видимости нельзя использовать имена функций из библиотеки C. Ни в C, ни в C++!
Иначе вы можете столкнуться не только с ODR-violation, но еще и с удивительным поведением компиляторов, умеющих отпимизировать распространненные конструкции. Иначе вы можете столкнуться не только с ODR-violation, но еще и с удивительным поведением компиляторов, умеющих оптимизировать распространенные конструкции.
Так, если определить свой собственный `memset`: Так, если определить свой собственный `memset`:
@@ -41,7 +41,7 @@ int main(){
При сборке такого примера со статически влинкованной стандартной библиотекой C, программа [упадет](https://godbolt.org/z/sq9bqhn46). При сборке такого примера со статически влинкованной стандартной библиотекой C, программа [упадет](https://godbolt.org/z/sq9bqhn46).
Так как вместо адреса стандартной функции `read` будет подставлен адрес глобальной переменной `read`. Аналогичный пример с использованием имени `write` предлагается читателю воплотить самостоятельно в качестве упражнения. Так как вместо адреса стандартной функции `read` будет подставлен адрес глобальной переменной `read`. Аналогичный пример с использованием имени `write` предлагается читателю воплотить самостоятельно в качестве упражнения.
Запретных имен много. Например, все, что начинается с `is*`, `to*` или `_*` запрещено в глобальном простанстве. `_[A-Z]*` запрещены вообще везде. POSIX резервирует имена, заканчивающиеся на `_t`. И еще много всего неожиданного. Запретных имен много. Например, все, что начинается с `is*`, `to*` или `_*` запрещено в глобальном пространстве. `_[A-Z]*` запрещены вообще везде. POSIX резервирует имена, заканчивающиеся на `_t`. И еще много всего неожиданного.
С более полными списками можно ознакомиться по ссылкам. С более полными списками можно ознакомиться по ссылкам.
Если вы пользуетесь запрещенными именами, то сегодня может всё работать, но не завтра. Если вы пользуетесь запрещенными именами, то сегодня может всё работать, но не завтра.

View File

@@ -2,7 +2,7 @@
C++ восхитительный язык. В нем столько идиом, концепций, и каждая со своей замечательной, иногда невыговариваемой, аббревиатурой! А самое замечательное в них то, что они иногда конфликтуют. И от их конфликта страдать придется разработчику. А иногда они вступают в симбиоз и страдать приходится еще больше. C++ восхитительный язык. В нем столько идиом, концепций, и каждая со своей замечательной, иногда невыговариваемой, аббревиатурой! А самое замечательное в них то, что они иногда конфликтуют. И от их конфликта страдать придется разработчику. А иногда они вступают в симбиоз и страдать приходится еще больше.
В C++ есть конструкторы, деструкторы и приходящая с нимим концепция RAII: В C++ есть конструкторы, деструкторы и приходящая с ними концепция RAII:
Захватывай и инициализируй ресурс в конструкторе, очищай и отпускай в деструкторе. И будет тебе счастье. Захватывай и инициализируй ресурс в конструкторе, очищай и отпускай в деструкторе. И будет тебе счастье.
Ну что ж, давайте попробуем! Ну что ж, давайте попробуем!
@@ -14,7 +14,7 @@ struct Writer {
public: public:
static const size_t BufferLimit = 10; static const size_t BufferLimit = 10;
// захватывем устройство, в которое будет писать // захватываем устройство, в которое будет писать
Writer(std::string& dev) : device_(dev) { Writer(std::string& dev) : device_(dev) {
buffer_.reserve(BufferLimit); buffer_.reserve(BufferLimit);
} }
@@ -84,7 +84,7 @@ std::cout << text;
Помните, мы обсуждали [не работающее перемещение](../syntax/move.md)? И выясняли, что в C++ нет деструктивного перемещения. А оно все-таки есть. Иногда. Когда срабатывает оптимизация возвращаемого значения и удаление лишних вызовов конструкторов копий/перемещений. Помните, мы обсуждали [не работающее перемещение](../syntax/move.md)? И выясняли, что в C++ нет деструктивного перемещения. А оно все-таки есть. Иногда. Когда срабатывает оптимизация возвращаемого значения и удаление лишних вызовов конструкторов копий/перемещений.
Программы выше все неправильные. Они предполагают, что деструктор `Writer` будет вызван до вовзрата значения из функции. Чего никак быть не может. Деструкторы объектов вызываются всегда после возврата из функции. Иначе эти самые значения бы просто умирали, и вызывающий код всегда получал мертвый объект. Программы выше все неправильные. Они предполагают, что деструктор `Writer` будет вызван до возврата значения из функции. Чего никак быть не может. Деструкторы объектов вызываются всегда после возврата из функции. Иначе эти самые значения бы просто умирали, и вызывающий код всегда получал мертвый объект.
Но как же тогда оно иногда работает и скрывает такую печальную ошибку? Но как же тогда оно иногда работает и скрывает такую печальную ошибку?
А вот как: А вот как:
@@ -117,7 +117,7 @@ const auto text = []{
// text пуст // text пуст
``` ```
Никакого неопереденного поведения тут, повторяю, нет. Просто всякий деструктор/конструктор с побочными эффектами как бы «сломан» из-за разрешенных и описанных в стандарте (и даже иногда гарантированных) оптимизаций. Никакого неопределенного поведения тут, повторяю, нет. Просто всякий деструктор/конструктор с побочными эффектами как бы «сломан» из-за разрешенных и описанных в стандарте (и даже иногда гарантированных) оптимизаций.
Ну а в каком-нибудь Rust нам такую ерунду написать [просто не дадут](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c5c9b4edbf891d469214eae29a3ca1af). Такие дела. Ну а в каком-нибудь Rust нам такую ерунду написать [просто не дадут](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c5c9b4edbf891d469214eae29a3ca1af). Такие дела.

View File

@@ -1,7 +1,7 @@
# Static initialization order fiasco # Static initialization order fiasco
Проблемы с использованием объектов до окончания их полной инициализации наигрывается во многих языках программирования. Сомнительный дизайн с разрывом объявления, конструирования и инициализации можно воплотить в жизнь чуть ли ни где угодно. Но обычно для этого все-таки надо Проблемы с использованием объектов до окончания их полной инициализации наигрывается во многих языках программирования. Сомнительный дизайн с разрывом объявления, конструирования и инициализации можно воплотить в жизнь чуть ли ни где угодно. Но обычно для этого все-таки надо
приложить некоторые усилия. А в C/C++ можно вляпатся незаметно, случайно и очень долго об этого не подозревать. приложить некоторые усилия. А в C/C++ можно вляпаться незаметно, случайно и очень долго об этого не подозревать.
В C/C++ мы можем разделять код программы по разным, независимым единицам трансляции В C/C++ мы можем разделять код программы по разным, независимым единицам трансляции
(в разные .c/.cpp файлы). Они могут компилироваться параллельно. (в разные .c/.cpp файлы). Они могут компилироваться параллельно.
@@ -106,7 +106,7 @@ int main() {
Чтобы избежать подобного, можно либо в конструкторе `TestStatic` вызвать функцию Чтобы избежать подобного, можно либо в конструкторе `TestStatic` вызвать функцию
`static_name` — тогда конструктор строки завершится до завершения конструктора `TestStatic` и `static_name` — тогда конструктор строки завершится до завершения конструктора `TestStatic` и
порядок уничтожения объктов будет другим. порядок уничтожения объектов будет другим.
Либо (и так иногда делают) в принципе предотвратить уничтожение статической строки: [создать ее в куче](https://godbolt.org/z/j7aY7q). Либо (и так иногда делают) в принципе предотвратить уничтожение статической строки: [создать ее в куче](https://godbolt.org/z/j7aY7q).
@@ -195,5 +195,5 @@ g++ -std=c++17 -o test2 logger.cpp main.cpp
В своей заботе о минимизации зависимостей и размере обработанных препроцессором исходников (или просто последовав совету линтера), вы не включили `iostream` в интерфейсный заголовок библиотеки, но использовали его в реализации. Пользователь, не знающий об этом, получает проблемы. Не самое удачное решение. В своей заботе о минимизации зависимостей и размере обработанных препроцессором исходников (или просто последовав совету линтера), вы не включили `iostream` в интерфейсный заголовок библиотеки, но использовали его в реализации. Пользователь, не знающий об этом, получает проблемы. Не самое удачное решение.
Объекты стандартных потоков не единственная возможность для подобных ошибок. Любая библиотека, использующая глобальные статические объекты, не позаботивщаяся об их инициализации ДО любых действий пользователя — потенциальный источник проблем. Объекты стандартных потоков не единственная возможность для подобных ошибок. Любая библиотека, использующая глобальные статические объекты, не позаботившаяся об их инициализации ДО любых действий пользователя — потенциальный источник проблем.
Если вы автор библиотеки, внимательнее относитесь к проектированию ее интерфейса. В C++ он не ограничивается только лишь сигнатурами функций и описанием классов. Если вы автор библиотеки, внимательнее относитесь к проектированию ее интерфейса. В C++ он не ограничивается только лишь сигнатурами функций и описанием классов.

View File

@@ -37,7 +37,7 @@ struct Point {
Почему? Почему?
Это измение сломало ABI. Это изменение сломало ABI.
В С++ все типы делятся на тривиальные и нетривиальный. Тривиальные, в свою очередь, бывают еще и в разных аспектах тривиальными. В общем случае тривиальность позволяет не генерировать дополнительный код, чтобы что-то сделать. В С++ все типы делятся на тривиальные и нетривиальный. Тривиальные, в свою очередь, бывают еще и в разных аспектах тривиальными. В общем случае тривиальность позволяет не генерировать дополнительный код, чтобы что-то сделать.
@@ -70,7 +70,7 @@ struct TNCopyable {
static_assert(!std::is_trivially_copyable_v<TNCopyable>); static_assert(!std::is_trivially_copyable_v<TNCopyable>);
// Здесь будет возврат через регистр rax. TCopyably в него как раз помещается // Здесь будет возврат через регистр rax. TCopyable в него как раз помещается
extern TCopyable test_tcopy(const TCopyable& c) { extern TCopyable test_tcopy(const TCopyable& c) {
return {c.x *5, c.y * 6}; return {c.x *5, c.y * 6};
} }
@@ -134,7 +134,7 @@ extern TNPoint zero_npoint() {
} }
``` ```
``` ```asm
zero_point(): # @zero_point() zero_point(): # @zero_point()
xorps xmm0, xmm0 xorps xmm0, xmm0
ret ret

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)) это влечет за собой неопределенное поведение.
Неожиданный вариант такого UB можно наблюдать на следующем примере (взято [тут](https://stackoverflow.com/questions/54120862/does-the-c-standard-allow-for-an-uninitialized-bool-to-crash-a-program)): Неожиданный вариант такого UB можно наблюдать на следующем примере (взято [тут](https://stackoverflow.com/questions/54120862/does-the-c-standard-allow-for-an-uninitialized-bool-to-crash-a-program)):
@@ -13,7 +13,7 @@ C и C++ — старые языки. В них можно легко и про
struct FStruct { struct FStruct {
bool uninitializedBool; bool uninitializedBool;
// Конструктор, не иницаилизирующий поля. // Конструктор, не инициализирующий поля.
// Чтобы проблема воспроизвелась, конструктор должен быть определен в другой единице трансляции // Чтобы проблема воспроизвелась, конструктор должен быть определен в другой единице трансляции
// Можно сымитировать с помощью атрибута noinline // Можно сымитировать с помощью атрибута noinline
__attribute__ ((noinline)) __attribute__ ((noinline))
@@ -49,7 +49,7 @@ int main()
-------------- --------------
Так если осутствие неинициализированных переменных способствует оптимизациям, почему бы их не запретить совсем, с жесткой ошибкой компиляции? Так если отсутствие неинициализированных переменных способствует оптимизациям, почему бы их не запретить совсем, с жесткой ошибкой компиляции?
Во-первых, они позволяют экономить на спичках: Во-первых, они позволяют экономить на спичках:
@@ -122,4 +122,4 @@ auto x = [&] { ... return value }();
### И последнее ### И последнее
Если к вам когда-нибудь придет светлая мысль использовать неинициализированную память в качестве источника случайности, гоните её как можно быстрее! Некоторые пробовали — [не получилось](https://kqueue.org/blog/2012/06/25/more-randomness-or-less/). Если к вам когда-нибудь придет светлая мысль использовать неинициализированную память в качестве источника случайности, гоните её как можно быстрее! Некоторые пробовали — [не получилось](https://kqueue.org/blog/2012/06/25/more-randomness-or-less/).

View File

@@ -2,7 +2,7 @@
Поддержка работы с коллекциями как с кастомными, так и со стандартными в C++ от версии к версии все лучше и лучше. Поддержка работы с коллекциями как с кастомными, так и со стандартными в C++ от версии к версии все лучше и лучше.
Алгоритмы стандратной библиотеки образца 11 — 17 стандартов работали и работают с парами итераторов, задающих диапазон элементов коллекции. Алгоритмы стандартной библиотеки образца 11 — 17 стандартов работали и работают с парами итераторов, задающих диапазон элементов коллекции.
```C++ ```C++
const std::vector<int> v = {1,2,3,4,5}; const std::vector<int> v = {1,2,3,4,5};
@@ -128,7 +128,7 @@ struct Numbers {
std::cout << *pos; std::cout << *pos;
``` ```
В С++20 наконец-то все пофиксили. Нет, старые STL-алгоритмы все также не работают. Просто теперь есть новые STL-алгоритмы, почти такие же, как старые, толко в пространстве имен `std::ranges` и жестко требующие удовлетворения новых концептов итераторов. В С++20 наконец-то все пофиксили. Нет, старые STL-алгоритмы все также не работают. Просто теперь есть новые STL-алгоритмы, почти такие же, как старые, только в пространстве имен `std::ranges` и жестко требующие удовлетворения новых концептов итераторов.
Поэтому пример ниже слегка распухает. Поэтому пример ниже слегка распухает.
```C++ ```C++
@@ -172,7 +172,7 @@ struct Numbers {
}; };
``` ```
С ними компилируется и [работает](https://godbolt.org/z/efh3qsxMd) С ними компилируется и [работает](https://godbolt.org/z/efh3qsxMd)
``` C++ ```C++
auto nums = Numbers(10); auto nums = Numbers(10);
auto pos = std::ranges::find_if(nums.begin(), nums.end(), [](int x){ return x % 7 == 0;}); auto pos = std::ranges::find_if(nums.begin(), nums.end(), [](int x){ return x % 7 == 0;});
@@ -230,13 +230,13 @@ struct Numbers {
std::vector<size_t> perm = { 1, 2, 3, 4, 5, 6, 7, 8, 9}; std::vector<size_t> perm = { 1, 2, 3, 4, 5, 6, 7, 8, 9};
std::ranges::shuffle(perm, std::mt19937(std::random_device()())); std::ranges::shuffle(perm, std::mt19937(std::random_device()()));
// И нам поуступают запросы на поиск позиции элемента, заведомо находящегося в векторе // И нам поступают запросы на поиск позиции элемента, заведомо находящегося в векторе
// size_t p = 7; // size_t p = 7;
assert(p < perm.size()) assert(p < perm.size())
return std::ranges_find(perm.begin(), std::unreachable_sentinel, p) - perm.begin(); return std::ranges_find(perm.begin(), std::unreachable_sentinel, p) - perm.begin();
``` ```
Очевидно, это крайне небезопасный ход. К которому стоит пребегать только в случае, если вы точно все проверили и эта оптимизация критична и необходима. Если в примере выше по какой-то причине будет запрошен элемент, не присутствующий в векторе, мы получим [неопределенное поведение](https://godbolt.org/z/459Y68PcW). Очевидно, это крайне небезопасный ход. К которому стоит прибегать только в случае, если вы точно все проверили и эта оптимизация критична и необходима. Если в примере выше по какой-то причине будет запрошен элемент, не присутствующий в векторе, мы получим [неопределенное поведение](https://godbolt.org/z/459Y68PcW).
Рефакторинг больших участков кода, использующего подобные фичи, может закончиться поиском трудноуловимых багов. В отличие от Rust, в C++ мы не можем гарантированно пометить участок кода, как потенциально опасный и проблемный. В C++ любой участок кода потенциально небезопасен и подчеркнуть это можно только комментарием или какими-нибудь ухищрениями в именовании функций или переменных. Рефакторинг больших участков кода, использующего подобные фичи, может закончиться поиском трудноуловимых багов. В отличие от Rust, в C++ мы не можем гарантированно пометить участок кода, как потенциально опасный и проблемный. В C++ любой участок кода потенциально небезопасен и подчеркнуть это можно только комментарием или какими-нибудь ухищрениями в именовании функций или переменных.

View File

@@ -31,10 +31,10 @@ std::cout << 10; // опять `a` ?!?!
поток, с переставленными флагами форматирования. Или вы забудете вернуть их в исходное поток, с переставленными флагами форматирования. Или вы забудете вернуть их в исходное
состояние. состояние.
Использование одного и того же имени метода для выстывления и получения флагов Использование одного и того же имени метода для выставления и получения флагов
тоже радует. Особенно любителей возвращать значения через `lvalue`-ссылки в аргументах функций. Но это фишка дизайна чуть ли не всего функционала по настройке потоков. Так что терпим. тоже радует. Особенно любителей возвращать значения через `lvalue`-ссылки в аргументах функций. Но это фишка дизайна чуть ли не всего функционала по настройке потоков. Так что терпим.
Ну и, конечно, стейт форматированя — дополнительная возможность пострелять по ногам в многопоточной среде. Ну и, конечно, стейт форматирования — дополнительная возможность пострелять по ногам в многопоточной среде.
## Грабли вторые. Глобальная локаль. ## Грабли вторые. Глобальная локаль.