1. C++ / Говнокод #9512

    +1009

    1. 1
    mExpanded = onExpandClick ? !mExpanded : mExpanded;

    Запостил: lifemaker, 22 Февраля 2012

    Комментарии (69) RSS

    • отчего некий процент кодеров так любит тернарность?
      им бы дать волю, они бы и циклы и сложения тернарным бы записывали
      Ответить
      • if(onExpandClick) mExpanded = !mExpanded; не намного и короче, хотя да, тернарный оператор тут как четвертая нога трехногому человеку...
        Ответить
    • хаккир просто позабыл про ксор, потому доставил недостаточно
      mExpanded ^= onExpandClick;
      Ответить
      • Ну кстати, если был бы логический XOR, а не только побитовый, то было бы приятно.

        mExpanded ^^= onExpandClick;
        Ответить
        • ээ, для bool это как раз и есть логический xor
          Ответить
          • >ээ, для bool это как раз и есть логический xor
            Ох, если бы так... ^^
            http://ideone.com/nOjUL
            Ответить
            • ну а разница, вам никто не мешает написать bool c=a^b
              Ответить
              • ну а разница, вам никто не мешает написать bool c=a|b вместо положенного bool c=a||b

                Таже визуал студия за такое выдаст performance warning, затребовав вместо if(a^b) написать if((a^b)!=0)

                Логические операции то не просто так придумали.
                Ответить
        • Логический xor: (a != b)
          Ответить
          • Нет. Твой "логический xor" возвращает true на a=5 b=6, а настоящий логический xor вернет false.

            Через '!=' правильнее будет '!a != !b'. Это - логический xor. Но такой вариант, к сожалению, не создает точки следования между операндами (как это делают '&&' и '||).

            Поэтому еще правильнее может быть '(!a && b) || (!b && a)'. Но этот вариант, к сожалению, страдает множественным вычислением операндов.

            Что делать? А вот что: 'a ? !b : b'. Это - логический xor с точкой следования внутри и одноразовым вычислением операндов.

            Не узнаете? Смотрим на код исходного автора и понимаем, что человек, вполне возможно, руководствовался вполне здравыми соображениями.
            Ответить
            • >Но такой вариант, к сожалению, не создает точки следования между операндами (как это делают '&&' и '||).
              А чем это грозит? Порядок вычисления операндов не определён?

              > 'a ? !b : b'. Это - логический xor с точкой следования внутри и одноразовым вычислением операндов.
              b разве 2 раза не вычисляется?
              Ответить
              • Грозит тем, чем обычно: неопределенным поведением.

                Например, если кто-то определил макро 'XOR(a, b)' как '(!(a) != !(b))', то выражение 'XOR(i++, i)' будет порождать неопределенное поведение. А если макро определено как '((a) ? !(b) : (b))', то поведение определено, первый операнд гарантированно вычисляется до второго и их вычисление разделено точкой следования.

                Одним из свойств оператора '?:' является то, что он вычисляет одну и только одну из своих условных подветок. Поэтому в данном определении операнд 'b' вычисляться будет строго один раз и строго после вычисления 'a'.
                Ответить
                • Поучительный не говнокод. Плюсую. Спасибо.
                  Ответить
                • XOR(i++, i) и должен порождать неопределённое поведение.
                  Ответить
                  • Что он там кому "должен" я не знаю. Главное, что раз 'i++ || i' не порождает неопределенного поведения, то и 'i++ xor i' тоже не следует его порождать.
                    Ответить
                    • Почему же? "или" может хватить для вычисления одного операнда, а "ксору" надо оба. Тогда надо чтоб левый операнд вычислялся всегда первым.
                      Ответить
                      • Именно так. В реализации 'a xor b' через 'a ? !b : b' левый операнд, т.е. 'a', вычисляется всегда первым.
                        Ответить
                        • Интересно получилось. Я с вами не согласен. А вы согласны с моим несогласием?
                          Ответить
                          • Все согласны с тем, что ты мелкий неумный тролль.
                            Ответить
                          • С чем именно "не согласен"? С тем, что в выражении 'a ? !b : b' операнд 'a' гарантированно вычисляется первым? Если да, то с этим в спецификацию языка - там все это четко написано.
                            Ответить
                      • Не надо. C не Java.
                        Ответить
                    • Скажем так — он никому не «должен» порождать определённое поведение. Как и вызов любой функции f(i++, i) (|| — не функция).
                      Ответить
            • Кстати, здесь написано
              http://www.rsdn.ru/forum/cpp/1875545.aspx?tree=tree
              Из процитированного выше (5/4) непосредственно следует некорректность приведенного кода. 
              Однако по той же причине некорректной является общепринятая идиома "сквозного присваивания":
              a = b = 1;

              Что это? Бред или так и есть?
              Ответить
              • Таки да, у Алены написано:
                >Несколько присваиваний подряд
                Интересный момент с таким кодом:
                int a=1, b=2, c=3;
                a=b=c=0;
                Стандарт как-то очень нечетко описывает такую ситуацию. Он говорит, что операция должна происходить справа налево, но ничего не говорит дополнительно по поводу того, когда должен быть результат этой операции записан в переменную. Точек следования внутри выражения нет, что значит, что компилятор может теоретически творить здесь что угодно и не обязательно все переменные в итоге будут равны 0. Так что, опять же теоретически, здесь имеет место unspecified behavior.
                Ответить
              • Это бред. Никакой неопределенности в 'a = b = 1' нет. Человек, который это писал, думал, что 'a = b = 1' означает комбинацию 'b = 1' и 'a = b', т.е. полагал, что значение должно [успеть] физически пройти через переменную 'b'. Отсюда и его выводы о неопределенности.

                В реальности же 'a = b = 1' означает комбинацию 'b = 1' и 'a = (type_of_b) 1'. Т.е. оба присваивания в данном случае делаются независимо и не используют переменную 'b' в качестве "посредника". Никакой неопределенности тут нет.
                Ответить
                • У Алены похожее написано:
                  http://alenacpp.blogspot.com/2005/11/sequence-points.html

                  Впрочем, я с вами соглашусь. Похоже на бред.
                  Ответить
            • Здесь пишут
              http://alenacpp.blogspot.com/2005/11/sequence-points.html


              (1.9/18) После первого выражения (здесь оно называется 'a') в следующих конструкциях:
              a || b 
              a && b
              a , b
              находятся точки следования
              
              НО! Правило слево-направо не работает для переопределенных операторов. 
              В этом случае переопределенный оператор ведет себя как обычная функция.
              То есть точка следования между этими операторами пропадает после перегрузки этих операторов? То есть порядок вычисления аргументов этих операторов не определён? То есть уходит ленивость этого оператора? Что-за крестопроблемы... >_>
              Ответить
              • Да, позволить перегружать эти операторы — большая глупость.
                Ответить
                • А чего нельзя было сделать так, чтобы при перегрузке эти операторы не теряли своё поведение (не теряли свои точки следования)?

                  Кстати a , b активно используется в BOOST::ASSIGN. Все же перегрузка полезная.

                  Перегрузка операторов
                  a || b
                  a && b
                  нужна для создания своей обертки над bool или своих булевых алгебр, например BOOST::TRIBOOL. Тоже полезная перегрузка.
                  Ответить
                  • Нельзя было. В общем случае перегрузка должна раскрываться в f(a)?g(a):h(a,b) и реализовываться не одной, а тремя функциями. Тогда можно было бы и тернарную логику организовывать, и векторные операции.

                    Сейчас же перегруженные || и && теряют свою семантику (превращаясь в | и &). Поэтому лучше их никогда-никогда не использовать.
                    Ответить
                    • Ввели бы ленивые функции уж в язык, специально, чтобы можно было перегружать эти операторы.
                      Ответить
                      • ага, в Haskell вон вообще свой кошерный if можно написать, ибо всё ленивое.
                        Ответить
                        • Haskell настолько ленив, что всё придется писать самому?
                          Ответить
                          • можно написать, а не нужно :)
                            Мне вот, к примеру пришлось недавно на жабе метод написать, который берёт список объектов, разбивает их на пары и попарно сравнивает (null-безопасным сравнением). Возвращает true, если объекты в парах равны. Проблемы начинаются, когда в метод нужно передавать объекты, являющиеся результатом вычислений. Если вычисления ресурсоёмкие (а такое бывает довольно часто), а сравнение прекратится до того момента, как дело дойдёт до результатов долгих вычислений, много тактов будет потрачено впустую.
                            в лиспе для оптимизации можно использовать простенький макрос, в хаскеле же практически любая чистая функция, реализующая этот функционал, будет работать правильно автоматически.
                            Ответить
                            • С многопоточностью не работал, поэтому сложновато воспринимается. В принципе понятно, ладно, будет момент - пойму не только поверхностно.
                              Ответить
                              • дело не в многопоточности, а в порядке вычисления аргументов: в джаве все аргументы вычисляются до передачи в функцию (аппликативный порядок), в ленивых языках передаются лишь "обещание" вычислить аргумент, когда потребуется его значение (нормальный порядок, ленивое вычисление). Поэтому лишних вычислений в этом примере практически не последует.
                                Ответить
                                • Понял. А можно пример? Для закрепления))))
                                  щас я себя минусану за глупость
                                  Ответить
                                  • Проще всего показать на функциях с побочными эффектами.

                                    вариант с джавой:
                                    http://ideone.com/ETwvi
                                    (обратим внимание: сообщения печатаются оба раза)
                                    вариант с макросом на clojure:
                                    http://ideone.com/K0quX
                                    (а вот тут сообщения печатаются только второй раз, когда первые два объекта совпадают)
                                    функция на Haskell выглядела бы так:
                                    pairsEqual :: (Eq t) => [t] -> Bool
                                    pairsEqual []       = True
                                    pairsEqual [x]      = error "Element count must be even"
                                    pairsEqual (x:y:xs) = (x == y) && (pairsEqual xs)
                                    Но вот запихнуть в неё выражения с побочными эффектами скорее всего не получится. Поэтому покажу вот на таком примере:
                                    pairsEqual [1, 2, someLongCalculation, anotherLongCalculation]
                                    Так вот, выражения someLongCalculation и anotherLongCalculation вычислены не будут.
                                    Ответить
                      • Это была бы настолько сильная переделка языка, что всё введённое до этого в C++ было бы мелочью. Есть, конечно, языки, где это возможно и уместно, но Си для этого плохая база.
                        Ответить
            • Вообще, насколько все поменялось с введением нового стандарта С++11? Старые правила ещё можно применять? Какие-то новые добавились, чтобы облегчить нам жизнь?
              Ответить
              • Кстати, вот что говорили на хабре по поводу отмены точек следования в новом стандарте С++11:
                Ну я так понял, что просто согласно принципу бритвы Оккама убрали одно понятие. Раньше было так:
                «операция »," является точкой следования ->точка следования означает, что все побочные эффекты к моменту операции должны быть закончены->значит всё, что написано до запятой гарантировано выполнится до того, что написано после запятой"

                стало:
                «операция »," гарантирует, что всё, что написано до запятой гарантировано выполнится до того, что написано после запятой"

                меньше понятий, меньше букв.
                Ответить
                • На том же Хабре писали, что понятие «точка следования» разнообразили и сделали более конкретным, т.е. теперь есть разные «специализированные» точки следования.

                  Что-то мне кажется, что про С++11 можно не думать еще года 2-3: стандарт-то приняли, а как с компиляторами дела обстоят?
                  Ответить
                  • mingw, gcc уже почти все держит
                    Точки следования ещё не готовы.
                    http://gcc.gnu.org/gcc-4.7/cxx0x_status.html
                    Ответить
            • Что-то у меня создается впечатление, что все проблемы связанные с точками следования создаются из-за выражений вида
              j=j++ + a
              n=(n=a)+b
              По крайней мере только эти выражения фигурируют в примерах.
              Сразу бы все проблемы ушли, если бы их (j++ и возможности присваивать результат операции = (присвоения) ) не было.
              Проблемы с оптимизацией бы тоже ушли, которые есть из-за этих точек следования, из-за чего в C++11 снова перелопатили стандарт, пытаясь усилить оптимизацию, добавив в стандарт новые сущности и различные исключения из правил.
              Ответить
              • Да, постинкремент — это такая хакерская штучка, введённая, чтобы приблизить Си к ассемблеру.
                Ответить
                • А каким образом это его к ассемблеру приближает? Если и приближает, то не к x86.

                  Вообще, от постинкремента больше проблем, чем пользы, впрочем как и от любых не чистых функций в правой части выражения.
                  Ответить
                  • Да, не x86. *p++=*s++ на PDP-11 реализовывалось одной машинной командой.
                    Ответить
            • Кстати, к вопросу о точках следования:
              f( new X, new Y );

              Понятно, что порядок конструирования объектов не определён. но допустим уже успел сконструироваться первый объект, а второй после этого сконструированный объект в конструкторе кинет исключение. Понятно, что память занятая под второй сконструированный объект при этом сама освободится. А вот что будет с первым? Тоже освободится сама или утечёт?
              Ответить
              • Утечет. Для этого в частности и придумали auto_ptr, а теперь уже unique_ptr.
                Ответить
            • Я здесь предполагал, что a и b - обе bool. Вариант a ? !b : b действительно лучше.
              Ответить
            • А нафига ксору точка следования посередине? Всё равно ксор полюбэ требует оба операнда, чтобы узнать значение.
              Ответить
              • Точка следования нужна не только для "ускоренного" вычисления (которое к XOR неприменимо, как ты правильно заметил), но и просто для того, чтобы выражения вида 'i++ xor i' имели конкретный смысл. Выражения 'i++ || i' и 'i++ && i' имеют конкретную спецификацию в C/C++. Поэтому хотелось бы, чтобы и 'i++ xor i' было определено. Чисто для единообразия.

                Я при этом не говорю, что выражения вида 'i++ || i' надо использовать в коде, но тем не менее иногда что-то подобное бывает весьма полезно.
                Ответить
                • i++ ^^ i

                  Няшненько... Как же С++/CLI этим раздражает...
                  Ответить
                • А выражения i++ + i и i++ * i не определены, поэтому для единообразия и с xor так же.
                  Ответить
                  • С чего бы это вдруг логический 'xor' должен быть единообразным с бинарными '+' и '*'???
                    Ответить
                    • наверное потому что ему необходимо знать оба операнда, прежде чем дать ответ
                      и оба этих операнда друг на друга не влияют, так что как раз на откуп компилятору отдан порядок их вычисления, как и + и * и т.д.
                      а для || и && компилятор обязан слева направо вычислять
                      Ответить
                    • А почему бы одной бинарной функции не быть единообразной со всеми другими?
                      Ответить
                      • все бинарные сделать единобезобразными?
                        Ответить
            • А зачем вам точка следования?
              Ответить
              • Ясно же написано: во избежание неопределённого поведения (которое потенциально может иметь место без нее).
                Ответить
                • Неопределённое поведение в языке не просто так.
                  Ответить
        • Зачем?
          Ответить

    Добавить комментарий