mirror of
https://github.com/Nekrolm/ubbook.git
synced 2026-06-09 13:14:18 +03:00
Remove trailing spaces
This commit is contained in:
+26
-26
@@ -5,7 +5,7 @@
|
||||
А иногда будет неопределенное поведение, segfault, и прочие радости жизни.
|
||||
|
||||
Разница между этими "иногда" в том, что есть настоящие константы, попытка модификации которых — UB.
|
||||
А есть ссылки на константы, ссылающиеся не на константы.
|
||||
А есть ссылки на константы, ссылающиеся не на константы.
|
||||
И раз на самом деле объект неконстантен, то модифицировать его можно без проблем.
|
||||
|
||||
Так, например, эту "фичу" можно [эксплуатировать](https://godbolt.org/z/oW4YeP), чтобы не повторять один и тот же код для `const` и не-`const` методов класса
|
||||
@@ -27,8 +27,8 @@ public:
|
||||
}
|
||||
|
||||
int& get_for_val_or_abs_val(int val) {
|
||||
return const_cast<int&>( // отбрасываем const с результата.
|
||||
// находясь в неконстантном методе, мы знаем, что результат
|
||||
return const_cast<int&>( // отбрасываем const с результата.
|
||||
// находясь в неконстантном методе, мы знаем, что результат
|
||||
// в действительности не является константой — проблем не будет.
|
||||
std::as_const(*this) // навешиваем const, чтобы вызвать const-метод,
|
||||
// а не уйти в бесконечную рекурсию
|
||||
@@ -46,7 +46,7 @@ private:
|
||||
По возможности, стоит избегать такого кода. Видно, что он очень хрупок — забытый или случайно удаленный
|
||||
`std::as_const` ломает его. И без настройки предупреждений, компиляторы об этом сообщать [не торопятся](https://godbolt.org/z/17fc31).
|
||||
|
||||
Вместо использования `const_cast` и привнесения в мир C++ еще большей нестабильности,
|
||||
Вместо использования `const_cast` и привнесения в мир C++ еще большей нестабильности,
|
||||
решить проблему диблирования кода можно с помощью шаблонного метода:
|
||||
```C++
|
||||
class MyMap {
|
||||
@@ -89,7 +89,7 @@ private:
|
||||
|
||||
Операции над иммутабельными данными отлично оптимизируются, распараллеливаются, и вообще ведут себя здорово.
|
||||
|
||||
Однако, возможность заигрывать со снятием и навешиванием `const` где угодно в коде,
|
||||
Однако, возможность заигрывать со снятием и навешиванием `const` где угодно в коде,
|
||||
вообще говоря, исключает этот ряд оптимизаций. Так повторное обращение по константной ссылке
|
||||
к одному и тому же полю или методу совсем не обязано кэшироваться.
|
||||
|
||||
@@ -100,8 +100,8 @@ using predicate = bool (*) (int);
|
||||
|
||||
int count_if(const std::vector<int>& v, predicate p) {
|
||||
int res = 0;
|
||||
for (size_t i = 0; i < v.size(); ++i) { // значение v.size() нельзя
|
||||
// единожды сохранить в регистре
|
||||
for (size_t i = 0; i < v.size(); ++i) { // значение v.size() нельзя
|
||||
// единожды сохранить в регистре
|
||||
if (p(v[i])) { //конкретный p может иметь доступ к изменяемой ссылке на
|
||||
// этот же самый v
|
||||
++res;
|
||||
@@ -132,7 +132,7 @@ int main() {
|
||||
|
||||
Этот код очень плох. Он не должен нигде встречаться; его никто не пропустит на ревью. Но так теоретически написать можно, поэтому оптимизация невыполняется.
|
||||
|
||||
Если в качестве типа предиката использовать шаблонный параметр,
|
||||
Если в качестве типа предиката использовать шаблонный параметр,
|
||||
можно привести куда более изощренные примеры без привлечения глобальных переменных.
|
||||
|
||||
Учитывая ограниченные возможности на автоматическую оптимизацию, подобный цикл переписывают (делая ту самую работу, которую ждали от компилятора):
|
||||
@@ -141,9 +141,9 @@ int count_if(const std::vector<int>& v, predicate p) {
|
||||
int res = 0;
|
||||
|
||||
for (auto x : v) { // range-based-for не обращается к size()
|
||||
// а один раз считывает begin/end указатели и работает
|
||||
// а один раз считывает begin/end указатели и работает
|
||||
// с ними; разность begin - end не рассчитывается
|
||||
if (p(v[i])) {
|
||||
if (p(v[i])) {
|
||||
++res;
|
||||
}
|
||||
}
|
||||
@@ -154,10 +154,10 @@ int count_if(const std::vector<int>& v, predicate p) {
|
||||
|
||||
Вот менее тривиальный пример `const`, никак не способствующего оптимизации:
|
||||
```C++
|
||||
void find_last_zero_pos(const std::vector<int>& v,
|
||||
void find_last_zero_pos(const std::vector<int>& v,
|
||||
const int* *poiner_to_last_zero) {
|
||||
*poiner_to_last_zero = 0;
|
||||
for (size_t i = 0; i < v.size(); ++i) { // мы опять не можем один раз
|
||||
for (size_t i = 0; i < v.size(); ++i) { // мы опять не можем один раз
|
||||
// сохранить значение v.size()
|
||||
if (v[i] == 0) {
|
||||
// внутри вектора есть поля типа int* — begin, end
|
||||
@@ -169,7 +169,7 @@ void find_last_zero_pos(const std::vector<int>& v,
|
||||
}
|
||||
```
|
||||
|
||||
Оставаясь в рамках рекомендуемых практик написания C++ программ,
|
||||
Оставаясь в рамках рекомендуемых практик написания C++ программ,
|
||||
мы не можем соорудить пример, который бы демонстрировал неприменимость оптимизации — нам мешает инкапсуляция.
|
||||
До приватных полей вектора мы не можем законно добраться.
|
||||
|
||||
@@ -184,12 +184,12 @@ int main() {
|
||||
И вот мы имеем парадоксальный результат: возможность написать явно некорректный код, запрещает компилятору оптимизировать цикл!
|
||||
И вся концепция неопределенного поведения как возможности для оптимизации (некорректного кода не бывает) разваливается.
|
||||
|
||||
Что ж, по крайней мере, для этого примера имеется некоторая стабильность:
|
||||
Что ж, по крайней мере, для этого примера имеется некоторая стабильность:
|
||||
Исходный цикл со счетчиком и переписанный на range-based-for закончатся на неопределенном поведении.
|
||||
|
||||
В современных языках (например, в Rust, благодаря семантике владения), все эти циклы могут быть успешно оптимизированы.
|
||||
|
||||
## Сonst, время жизни и происхождение указателей.
|
||||
## Сonst, время жизни и происхождение указателей.
|
||||
|
||||
Неизменяемые объекты всем хороши, кроме одного — это константные объекты в C++.
|
||||
Если они где-то засели, то их оттуда по-нормальному не выгонишь.
|
||||
@@ -205,7 +205,7 @@ struct Unit {
|
||||
};
|
||||
```
|
||||
|
||||
Из-за константного поля объекты `Unit` теряют операцию присваивания.
|
||||
Из-за константного поля объекты `Unit` теряют операцию присваивания.
|
||||
Их нелья менять местами — `std::swap` не работает больше.
|
||||
`std::vector<Unit>` нельзя больше просто так отсортировать... В общем, сплошное удобство.
|
||||
|
||||
@@ -222,7 +222,7 @@ std::cout << unit.back().id << "";
|
||||
В зависимости от того, смогли ли при реализации вектора задушить агрессивные оптимизации компилятора, такой код может вывести
|
||||
либо `1 2 ` (все хорошо), либо `1 1 ` (компилятор соптимизировал константное поле!)
|
||||
|
||||
Компилятор имеет право воспринимать происходящее следующим образом:
|
||||
Компилятор имеет право воспринимать происходящее следующим образом:
|
||||
- В векторе 1 элемент
|
||||
- Вектор не реаллоцировался.
|
||||
- Указатель на элемет в первом `cout` и во втором `cout` один и тот же.
|
||||
@@ -232,7 +232,7 @@ std::cout << unit.back().id << "";
|
||||
- Вывожу закэшированное значение.
|
||||
|
||||
К сожалению или к счастью, воспроизвести подобное поведение компилятора на практике не получается.
|
||||
Тем не менее, вот такой код, который может использоваться для реализации самописных `std::optional`,
|
||||
Тем не менее, вот такой код, который может использоваться для реализации самописных `std::optional`,
|
||||
по стандарту содержит UB (и не одно!)
|
||||
|
||||
```C++
|
||||
@@ -251,14 +251,14 @@ std::cout << unit.back().id << "";
|
||||
using storage = std::aligned_storage_t<sizeof(Unit), alignof(Unit)>;
|
||||
storage s;
|
||||
auto p = new (&s) Unit{1,2};
|
||||
std::cout << rp->id << "\n";
|
||||
p->~Unit();
|
||||
std::cout << rp->id << "\n";
|
||||
p->~Unit();
|
||||
p = new (&s) Unit{2,2};
|
||||
std::cout << p->id << "\n";
|
||||
p->~Unit();
|
||||
p->~Unit();
|
||||
```
|
||||
|
||||
Но поддерживать указатель, взвращенный оператором `new`, не всегда возмножно.
|
||||
Но поддерживать указатель, взвращенный оператором `new`, не всегда возмножно.
|
||||
Он занимает место, его надо хранить, что неэффективно при реализации `optional`: для `int32_t` будет нужно в три раза больше места на 64хбитной системе (4 байта на `storage` + 8 байт на указатель)!
|
||||
|
||||
Поэтому в стандартной библиотеке с C++17 есть функция "отмывания" невесть откуда взявшихся указателей — `std::launder`.
|
||||
@@ -267,11 +267,11 @@ std::cout << unit.back().id << "";
|
||||
using storage = std::aligned_storage_t<sizeof(Unit), alignof(Unit)>;
|
||||
storage s;
|
||||
new (&s) Unit{1,2};
|
||||
std::cout << std::launder(reinterpret_cast<Unit*>(&s))->id << "\n";
|
||||
std::launder(reinterpret_cast<Unit*>(&s))->~Unit();
|
||||
std::cout << std::launder(reinterpret_cast<Unit*>(&s))->id << "\n";
|
||||
std::launder(reinterpret_cast<Unit*>(&s))->~Unit();
|
||||
new (&s) Unit{2,2};
|
||||
std::cout << std::launder(reinterpret_cast<Unit*>(&s))->id << "\n";
|
||||
std::launder(reinterpret_cast<Unit*>(&s))->~Unit();
|
||||
std::cout << std::launder(reinterpret_cast<Unit*>(&s))->id << "\n";
|
||||
std::launder(reinterpret_cast<Unit*>(&s))->~Unit();
|
||||
```
|
||||
|
||||
Так и при чем тут `const`? "Настоящая" константность (переменные и поля объявленные с `const`) вместе с UB при использовании "неправильных" указателей, как раз и позволяют компилятору производить описанные спецэффекты.
|
||||
|
||||
Reference in New Issue
Block a user