0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Теги: с++, to_be || !to_be, перегрузка логических операторов, isempty, a == nullptr, оптимизация логических вычислений, гарантия последовательного выполнения, порядок вычисления выражений</p>
1
<p>Теги: с++, to_be || !to_be, перегрузка логических операторов, isempty, a == nullptr, оптимизация логических вычислений, гарантия последовательного выполнения, порядок вычисления выражений</p>
2
<p>Чем так хорош язык C++? Конечно, возможностью перегрузки операторов для своего класса! Ну ладно, разумеется, не только этим, но это классная возможность, разве нет? Давайте поговорим о перегрузке логических операторов, стоит ли это делать и чем это грозит (кроме непонятного кода в результате).</p>
2
<p>Чем так хорош язык C++? Конечно, возможностью перегрузки операторов для своего класса! Ну ладно, разумеется, не только этим, но это классная возможность, разве нет? Давайте поговорим о перегрузке логических операторов, стоит ли это делать и чем это грозит (кроме непонятного кода в результате).</p>
3
class SomeClass { public: bool operator&&(const SomeClass& other) { // тут сложный и запутанный код, например, копирования // объекта other в this } };<p>Итак, дело сделано - у нашего класса переопределён оператор "&&". Если нам повезло, и наши коллеги не заблокировали такой код на review, то вполне можно им пользоваться всю оставшуюся карьеру. Мы получили клёвый способ копирования одного объекта в другой (чтобы враги не догадались, как работает наш код).</p>
3
class SomeClass { public: bool operator&&(const SomeClass& other) { // тут сложный и запутанный код, например, копирования // объекта other в this } };<p>Итак, дело сделано - у нашего класса переопределён оператор "&&". Если нам повезло, и наши коллеги не заблокировали такой код на review, то вполне можно им пользоваться всю оставшуюся карьеру. Мы получили клёвый способ копирования одного объекта в другой (чтобы враги не догадались, как работает наш код).</p>
4
<h2>А что мы потеряли?</h2>
4
<h2>А что мы потеряли?</h2>
5
<p>Потеряли мы оптимизацию логических вычислений и гарантию последовательного выполнения. О чем речь? Каждому хоть раз в жизни встречался вот такой код:</p>
5
<p>Потеряли мы оптимизацию логических вычислений и гарантию последовательного выполнения. О чем речь? Каждому хоть раз в жизни встречался вот такой код:</p>
6
Type* a = prepareA(); // получили указатель на некий тип откуда-то if (a && !a->isEmpty()) // пытаемся обработать …<h2>Заметили?</h2>
6
Type* a = prepareA(); // получили указатель на некий тип откуда-то if (a && !a->isEmpty()) // пытаемся обработать …<h2>Заметили?</h2>
7
<p>Вообще говоря, код небезопасен, потому что, если в переменной "а" лежит пустой указатель, вызов<strong>isEmpty</strong>должен привести в лучшем случае к аварийному завершению программы. Однако, это работает, так как выполняется оптимизация логического выражения. Если<strong>a == nullptr</strong>, то вторая часть выражения просто не будет выполняться.</p>
7
<p>Вообще говоря, код небезопасен, потому что, если в переменной "а" лежит пустой указатель, вызов<strong>isEmpty</strong>должен привести в лучшем случае к аварийному завершению программы. Однако, это работает, так как выполняется оптимизация логического выражения. Если<strong>a == nullptr</strong>, то вторая часть выражения просто не будет выполняться.</p>
8
<p>Таким образом, компилятор гарантирует, что, во-первых, первая часть выражения всегда будет исполнена первой (а мы помним, что в любом другом случае компилятор волен изменять порядок вычислений выражений), во-вторых, что вторая часть выражения выполнена не будет, если в этом не будет необходимости. Аналогичная логика работает и с оператором "||".</p>
8
<p>Таким образом, компилятор гарантирует, что, во-первых, первая часть выражения всегда будет исполнена первой (а мы помним, что в любом другом случае компилятор волен изменять порядок вычислений выражений), во-вторых, что вторая часть выражения выполнена не будет, если в этом не будет необходимости. Аналогичная логика работает и с оператором "||".</p>
9
<p>Согласно стандарту, как только компилятор встречает переопределённый оператор "&&" или "||", он волен больше не давать вышеобозначенной гарантии порядка вычисления выражений, а также обязан вычислить оба выражения независимо от из значений (делается это для того, чтобы выполнились все пост-эффекты вызовов таких операторов, даже если никаких пост-эффектов не закладывалось в реализации). Скорее всего, такое изменение поведения останется незаметным и будет безобидным, но помнить про него всё-таки желательно.</p>
9
<p>Согласно стандарту, как только компилятор встречает переопределённый оператор "&&" или "||", он волен больше не давать вышеобозначенной гарантии порядка вычисления выражений, а также обязан вычислить оба выражения независимо от из значений (делается это для того, чтобы выполнились все пост-эффекты вызовов таких операторов, даже если никаких пост-эффектов не закладывалось в реализации). Скорее всего, такое изменение поведения останется незаметным и будет безобидным, но помнить про него всё-таки желательно.</p>
10
<p>Дополнительный плюс - будет о чем поговорить за следующим чаепитием с коллегами.</p>
10
<p>Дополнительный плюс - будет о чем поговорить за следующим чаепитием с коллегами.</p>
11
<p><em>Есть что добавить? Напишите в комментариях!</em></p>
11
<p><em>Есть что добавить? Напишите в комментариях!</em></p>
12
12