mirror of
https://github.com/Nekrolm/ubbook.git
synced 2025-12-17 12:54:35 +03:00
fix typos and lang syntax highlight
This commit is contained in:
@@ -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) сравниваемых чисел.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|
||||||
|
|||||||
@@ -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` вычисления.
|
||||||
|
|||||||
@@ -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` — синтаксический сахар
|
||||||
|
|||||||
@@ -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`. И еще много всего неожиданного.
|
||||||
С более полными списками можно ознакомиться по ссылкам.
|
С более полными списками можно ознакомиться по ссылкам.
|
||||||
|
|
||||||
Если вы пользуетесь запрещенными именами, то сегодня может всё работать, но не завтра.
|
Если вы пользуетесь запрещенными именами, то сегодня может всё работать, но не завтра.
|
||||||
|
|||||||
@@ -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). Такие дела.
|
||||||
|
|
||||||
|
|||||||
@@ -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++ он не ограничивается только лишь сигнатурами функций и описанием классов.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
|||||||
|
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
Так если осутствие неинициализированных переменных способствует оптимизациям, почему бы их не запретить совсем, с жесткой ошибкой компиляции?
|
Так если отсутствие неинициализированных переменных способствует оптимизациям, почему бы их не запретить совсем, с жесткой ошибкой компиляции?
|
||||||
|
|
||||||
Во-первых, они позволяют экономить на спичках:
|
Во-первых, они позволяют экономить на спичках:
|
||||||
|
|
||||||
|
|||||||
@@ -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++ любой участок кода потенциально небезопасен и подчеркнуть это можно только комментарием или какими-нибудь ухищрениями в именовании функций или переменных.
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ std::cout << 10; // опять `a` ?!?!
|
|||||||
поток, с переставленными флагами форматирования. Или вы забудете вернуть их в исходное
|
поток, с переставленными флагами форматирования. Или вы забудете вернуть их в исходное
|
||||||
состояние.
|
состояние.
|
||||||
|
|
||||||
Использование одного и того же имени метода для выстывления и получения флагов
|
Использование одного и того же имени метода для выставления и получения флагов
|
||||||
тоже радует. Особенно любителей возвращать значения через `lvalue`-ссылки в аргументах функций. Но это фишка дизайна чуть ли не всего функционала по настройке потоков. Так что терпим.
|
тоже радует. Особенно любителей возвращать значения через `lvalue`-ссылки в аргументах функций. Но это фишка дизайна чуть ли не всего функционала по настройке потоков. Так что терпим.
|
||||||
|
|
||||||
Ну и, конечно, стейт форматированя — дополнительная возможность пострелять по ногам в многопоточной среде.
|
Ну и, конечно, стейт форматирования — дополнительная возможность пострелять по ногам в многопоточной среде.
|
||||||
|
|
||||||
## Грабли вторые. Глобальная локаль.
|
## Грабли вторые. Глобальная локаль.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user