Remove trailing spaces

This commit is contained in:
Lapshin Dmitry (LDVSOFT)
2021-01-25 17:34:48 +03:00
parent 1c3f97d4a9
commit 54cdebec35
20 changed files with 136 additions and 136 deletions
+26 -26
View File
@@ -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 при использовании "неправильных" указателей, как раз и позволяют компилятору производить описанные спецэффекты.