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

    0

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    struct A
    {
        A() { cout << "A::A()" << endl; }
        ~A() { cout << "~A::A()" << endl; }    
    };
    
    struct B
    {
        B() { cout << "B::B()" << endl; }
        ~B() { cout << "~B::B()" << endl; }    
    };
    
    union U
    {
        A a;
        B b;
        int n;
        
        U() { a = A {}; b = B {}; }
        ~U() {}
    };
    
    int main()
    {
        U u;
    }

    Запустить тут: cpp.sh/3ewfw

    Получается информация о том, какой сейчас объект активен в union где-то хранится.

    Запостил: OlegUP, 03 Июля 2020

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

    • Вообще, очень классно придумано с union'ами в XDR для RPC протокола.

      union CLOSE4res switch (nfsstat4 status) {
          case NFS4_OK:
                  stateid4       open_stateid;
          default:
                  void;
         };
      Ответить
    • Но в Сишечке нету конструкторов и деструкторов. Поэтому пользуясь union'ом, членами которого будут структуры с указателями на кучу как быть?
      Ответить
      • > Поэтому пользуясь union'ом, членами которого будут структуры с указателями на кучу как быть?
        Если всё же хочется поиграть со стрелковым оружием возле ноги, то как-то так:
        struct X {};
        struct Y {};
        enum UnionTag { TAG_X, TAG_Y };
        struct XYUnion {
            UnionTag tag;
            union {
                X x;
                Y y;
            };
        };
        Ответить
      • struct foo_t
        {
            char* ptr;    
        };
        
        struct bar_t
        {
            char* ptr;    
        };
        
        union petux_t
        {
            foo_t foo;
            bar_t bar;
        };
        
        struct s_t
        {
            uint32_t type;
            petux_t petux;
        }
        
        void set_petux_member(uint32_t type, void* instance, struct s_t* s)
        {
            switch s->type
            {
                case FOO:
                    destroy_foo(s->petux->foo);
                // ...
            }
            
            switch type
            {
                case FOO:
                    s->foo = *((foo_t*)instance);
                // ...
            };
        
            s->type = type;
        }


        Именно поэтому я за C++.
        Ответить
    • > Получается информация о том, какой сейчас объект активен в union где-то хранится.
      Нет, в этом и смысл крестового union'а.
      Главное правило работы с union: не используй union. Серьёзно, в нём настолько много неопределённой хуйни, что, если ты не выучил Стандарт от корки до корки, то обязательно выстрелишь себе в ногу.
      Реальный пример: https://wandbox.org/permlink/kL6PSZnyJRIrvQl7. В union'е из поста вместо данных хранится какая-то хуйня.
      Ответить
      • хмм
        Ответить
        • > как программа определяет какой деструктор вызывать

          Лол, никак. Она просто не скомпилится если там что-то надо будет вызывать.
          Ответить
          • Проблема в неконсистетности при расширениях языков.

            Все рекламируют «новую, крутую фичу» в «Новом Стандарте™», но редко кто до конца продумывает как она будет взаимодействовать со всеми остальными конструктами языка, существовавшими ранее.

            В случае современных языков вроде С++ и ECMAScript количество кобенаций разных фич растёт экспоненциально.

            В Сишке с юнионами проблем такого рода не было, потому что union изначально был в дизайне языка.

            А когда к спине рукав пришивают, тут уж извините.
            Ответить
            • >>А когда к спине рукав пришивают...
              Удобно же - манипулятор туда и жопу можно чесать)))
              Ответить
              • У павианов такой манипулятор есть. Рукав будет как раз к месту.
                Ответить
        • З.Ы. А в твоём примере ты явно сказал, что нихуя вызывать не надо, вот он и конпеляется.

          Добавь ещё немного логов в конструктор и деструктор юниона и всё поймёшь.
          Ответить
      • https://docs.microsoft.com/ru-ru/cpp/cpp/unions?view=vs-2019#unrestricted-unions-c11

        Ну вот тут описывается, как правильно его использовать.
        Ответить
      • > не используй union

        Ну да, есть же std::variant, который вполне понимает что в нём лежит и зовёт нужные конструкторы и деструкторы. Впизду union.
        Ответить
        • Подтверждаю.
          Ответить
        • показать все, что скрытоvanished
          Ответить
          • > Понимать может только программист.
            Опровергаю. Ситуации, когда тип значения никак нельзя узнать в компайл-тайме, существуют.
            Ответить
            • показать все, что скрытоvanished
              Ответить
              • > Программист пишет код, только он может знать чо куда пишет, и чо откуда может читать (и при каких условиях).
                В полностью статическом языке без «union», «variant» и прочих «void *» конпелятор тоже знает, что, куда и когда пишется. А в случае с недодинамической типизацией программист делится с конпелятором этим знанием: либо явно («tagged union»), либо бульмень неявно (std::variant).
                Ответить
                • показать все, что скрытоvanished
                  Ответить
                  • Переписал вообще без «variant» и заодно убрал возможные «UB», проверь.
                    struct Pituh {
                        int x;
                    
                        void set(int, int v)
                        {
                            x = v;
                        }
                    
                        int get(int)
                        {
                            return x;
                        }
                    };
                    static_assert(sizeof(Pituh) == 4);
                    Ответить
                    • показать все, что скрытоvanished
                      Ответить
                      • Пожалуйста.
                        struct Pituh {
                            int a = 0;
                            double b = 0;
                            void set(int k, int v1, double v2)
                            {
                                if (k % 2 == 0) {
                                    a = v1;
                                } else {
                                    b = v2;
                                }
                            }
                            void print(int k)
                            {
                                if (k % 2 == 0) {
                                    cout << a << endl;
                                } else {
                                    cout << b << endl;
                                }
                            }
                        };

                        Опять же, просто, безопасно и никаких UB.
                        Ответить
                        • показать все, что скрытоvanished
                          Ответить
                          • Почему 8 то?
                            Ответить
                          • Ну ладно, ладно, если без шутеечек, то суть в том, что использование union-а для экономии памяти — это в абсолютном большинстве случаев ненужная предварительная оптимизация, которая приводит только к говну в коде и куче потенциальных UB. Так делать не нужно.

                            А что касается Питуха, то экономия на спичках может быть оправдана только тогда, когда ты создаёшь буквально миллиарды этих самых питухов. И если уж такое произошло — сделай класс «Курятник», засунь в него variant<vector<float>, vector<int>> и получи надёжную и безопасную замену юнионоговну с оверхедом в восемь байт всего. Либо вообще лучше продумай рахитектуру и избавься от необходимости в этом динамоговне.
                            Ответить
                            • union нужен чтобы копаться в битовых кишках флоатов.
                              Но это можно всякими сазтами решить.
                              Ответить
                              • Причём, ЕМНИП, до недавнего времени это было де-юре UB. Вроде бы в последних версиях крестов/сишки это пофиксили, но я не уверен.
                                Ответить
                                • Ага. Но в целом, я когда-то замерял пирфоманс union с bit fields и рукоблудногописного сдвигового байтоёбства.
                                  И тогда компилер лучше питумизировал именно union с bit fields, а не ручную питушню.

                                  Так что union — царская штука. Он быстр и опасен.
                                  Ответить
                            • Юзай полиморфизмъ, а не союз!
                              Ответить
                              • Полиморфизм не нужон.
                                Нужен: disjoint union (A|B)

                                А обычное объединение можно заменить геттерами и внутренними кастами.
                                То есть всем плевать как хранятся те или иные данные, если геттеры отдают что надо.
                                Ответить
                              • Тьфу ты, точно, зациклился на союзах и стд::вореантах. А так это действительно получается классическая ООП-задачка.
                                Ответить
      • Там вроде информация хранится в голове у программиста, не?

        Ну типа ты явно меняешь активного члена посредством ручного вызова десктрутора и placement new.
        А если ты потрогал неактивного члена, то это UB.

        Если вдуматься, то все логично.
        Или нет?
        Ответить
        • Если потрогал неактивный член - UB.
          Если оставил член активным и свалил - UB.
          Ответить
          • Свалил, всмысле не вызвал ему явно деструктор?
            Ответить
            • Да. Он ведь какие-то ресурсы в конструкторе мог выделить или даже свой адрес кому-то сказать.
              Ответить
              • Ну это как если бы я в сишечке хранил там указатели на кучу, и при смене активного члена я бы делал явно malloc новому и free старому
                Ответить
          • показать все, что скрытоvanished
            Ответить
        • Именно поэтому я за Сишку.
          Там нет никаких «деструкторов» и «rtti».
          Ответить
          • В общем да: юнион в сишечке очень простой.
            А в C++ он стал сложным именно из за деструкторов и конструкторов.

            Кстати, если у меня дефолтные конструктор и деструктор которые ничего не делают, то эт же ничем не отличается от сишечки, не?
            Ответить
            • Да он ничем от сишного в общем-то не отличается. В этом и проблема.

              И если ты в него что-то сложнее сишной структуры положишь, то конпелятор откажется это конпелять. И тебе надо будет объявлять конструктор и деструктор вручную и управлять всем вручную.
              Ответить
              • Все таки RTTI породили кучу проблем.

                В сишечке никакой код "автоматически" не вызывается, и потому все намного проще.
                А как только ты привез в этот мир конструкторы и деструкторы, то у тебя сразу появился миллион вопросов.
                Ответить
                • Возможно, вы имели в виду «RAII»?
                  На самом деле проблема не в «RAII», а в попытке усидеть на джвух стульях: иметь статически типизированный язык с возможностью в одной и той же пельменной хранить значения разных типов, определяемых в рантайме.
                  Ответить
                  • да, именно raii. Макаки просто обезьянничают, я увидал rtti у Пи, и собезъянничал не включая мозг.

                    Конкретно тут проблема и в этом тоже, но в целом необходимость вызывать деструкторы делает язык сложнее.

                    Всякие конструкторы копирования, например.

                    То-есть в сишечке я четко понимаю, что вот тут есть данные, и а вот тут есть код.
                    В С++ (вообще в мире ООП) данные и код объеденены вместе, и манипуляция с данными приводит к вызову кода, или НЕ приводит (как в этом случае)
                    Ответить
                    • > но в целом необходимость вызывать деструкторы делает язык сложнее.
                      Не соглашусь. Если у тебя возникла необходимость вызывать деструктор вручную, значит, ты что-то делаешь сильно не так.
                      Собственно, мне на ум приходят только две ситуации, когда это необходимо делать: в обсуждаемом union-е с нетривиальными членами и при использовании «placement new». Первый вореант в 99% случаях можно заменить на std::variant и не ебать себе мозги, второй обычно означает какие-то очень хардкорные оптимизации, которые либо предварительные и не нужны вообще, либо которые можно завернуть в три слоя абсракций и забыть про них как про страшный сон.
                      Ответить
                      • Даже если ты не вызываешь его вручную, то всё равно у тебя вызывается какой-то код без явного заказа.

                        Когда из области видимости выходит любой сишный объект, то происходит ничего. Когда выходит С++ный объект, то вызывается код. Хотя ты его явно не вызвал.

                        Когда я передаю в функцию какой-то сишный объект, он туда тупо копируется (ну кроме несчастных массивов). Когда я делаю это с С++ объектом, то вызывается копирующий коснтруктор. Хотя я его явно не вызвал.

                        В целом это делает код сложнее, и приводит к таким вот ситуациям, когда placement new и деструктор надо вызывать явно.

                        С другой стороны я согласен, что конкретно тут дело в ненужном байтоёбстве.
                        То-есть мы спустились на такой низкий уровень, что абстракции старые уже не работают, и потому нужно делать все вручную.

                        Но в сишечке такой проблемы бы не возникло. Там все всегда делается одинаковым образом: вручную, и потому все просто. Хотя и бойлерплейтно.
                        Ответить
                        • В этом и весь смысл. Благодаря «RAII», компилятор тебе гарантирует (конечно, до тех пор, пока ты не спустился в глубокое байтоёбство — но тут уж ничего не поделать, это кресты/сишка, а не ЙАЖА, чтобы программиста за дурачка считать), что вся твоя память будет очищена, все открытые хэндлы закрыты, все сокеты остановлены и так далее.
                          Более того, «RAII» делает это детерминированно: ты всегда можешь точно сказать, когда тот или иной объект сконьструируется и когда он разрушится.

                          А уж сколько боли в сишечке доставляет необходимость выделить и освободить сразу несколько ресурсов, причём ещё учесть корректную обработку ошибок…
                          Ответить
                          • Появление любой автоматизации или абстракции всегда two folded:

                            С одной стороны у тебя много чего "просто работает правильно" без ручного пирдолинга.
                            С другой стороны система в целом становится сложнее, и если такая абстракция протекает, то приходится эту абстракцию понимать. Программист на С++ должен в целом больше абстракций понимать, чем программист на си.

                            RAII вот такая как раз абстракция, и вот тут она протекла.
                            В сишке такой абстракции нет, и протекать нечему.
                            Ответить
                            • > Программист на С++ должен в целом больше абстракций понимать, чем программист на си.
                              Ну тут безусловно, кресты в целом я не оправдываю, просто указываю, что «RAII» — это хорошая идея, одна из немногих, которые в крестах сделаны хорошо.

                              > В сишке такой абстракции нет, и протекать нечему.
                              Ну да, в сишке тебе в 100% случаях надо ручками закрывать все ресурсы и чистить вилкой утечки памяти в любом бульмень крупном проекте. В крестах такой ручной труд требуется в 0.01% случаях (и это само по себе признак, что, скорее всего, у тебя в архитектуре проблемы), всё остальное за тебя сделает конпелятор.
                              Ответить
              • > И если ты в него что-то сложнее сишной структуры положишь, то конпелятор откажется это конпелять. И тебе надо будет объявлять конструктор и деструктор вручную и управлять всем вручную.
                Кстати, самое смешное, что деструктор союза никак не может стандартными средствами узнать, что в этом союзе хранится. Для этого придётся либо оборачивать союз в структуру, либо использовать совсем уж неадекватные вещи вроде глобальных мап «адрес_союза->тип».
                Нахуя вообще сделали возможность создания деструктора союза — загадка.
                Ответить
                • > никак не может

                  Ну почему. Если у меня в юнионе все типы одинаково начинаются*, то я могу обращаться к общим полям через любой из типов. И если в этих общих полях есть какая-то инфа, по которой я могу определить тип - я могу запилить деструктор.

                  * емнип, там довольно длинное определение в стандарте.
                  Ответить
        • > Ну типа ты явно меняешь
          Тут проблема. Это вполне мог быть не я, а какой-то внешний кот. И чтобы узнать, какой именно член был потроган — нужно или городить костыли вроде упомянутых выше «tagged union», или не ебать себе мозги и использовать уже написанный, хороший и безопасный std::variant.
          Ответить
          • Я понимаю проблему, да)

            Просто сказал, что в целом это логично.

            У тебя есть один стул, и на него нужно усадить двух питухов.
            Когда ты сажаешь второго питуха туда, ты должен первого явно уничтожить, а второго явно усадить.
            Ответить
          • Нужен еще квантовый юнион - пока не взглянешь, то не узнаешь, что там лежит.
            Ответить
    • U() { a = A {}; b = B {}; }

      Вот тут уже́ смешно. Поскольку a и b — члены одного union'а, то операция b = B {}; затирает значение, хранящееся в a.

      Если сделать a и b не значениями, а указателями, тут вообще будет утечка.
      Ответить
      • Да это вообще UB походу: вызов оператора присваивания на несконструированном объекте.

        З.Ы. Ну и кстати обращение к b когда активно a - тоже UB.
        Ответить
        • Точно, это же не инициализация, а присваивание.
          Вот на это:
          U() :
              a(),
              b()
          {
              cout << "U()" << endl;
          }

          Конпелятор честно отвечает:
          prog.cc: In constructor 'U::U()':
          prog.cc:24:5: error: initializations for multiple members of 'U'

          А если убрать иницализацию b, то всё скомпилится, но деструктор ~A() не будет вызван вообще: https://wandbox.org/permlink/iDC9LcIUfbxuHugG — потому что компилятор и правда не может знать, что именно хранится в union. Именно поэтому я против «union».

          UPD: Из-за UB, видимо, и происходит хрень с мгновенным деконструированием оригинального кода.
          Вообще, если в скомпилированной крестовой программе происходит какая-то совершенно неадекватная хуйня, то с очень большой вероятностью это признак того, что где-то в коде притаилось UB.
          Ответить
        • А вообще, кажется, я понял: конструкторы и деструкторы в оригинальном коде вызываются для временных объектов, которые справа от операторов присваивания. Потом они молча присваиваются неинициализированным объектам (-ту) a/b (попутно вызывая UB) и разрушаются. Ну и в деструкторе ~U() ничего не происходит, потому что компилятор не знает, какой член надо разрушать.
          Ответить
          • > компилятор не знает

            Написав деструктор ты перешёл на ручное управление. Теперь ты отвечаешь за члены союза ;)

            З.Ы. Если бы автор не экономил на логах и расставил их вокруг всех строчек в конструкторе, то всё было бы очевидно.
            Ответить
          • > конструкторы и деструкторы в оригинальном коде вызываются для временных объектов
            Да.

            https://wandbox.org/permlink/dvcC9Ah2PLvNO17O
            Ответить
            • А всё потому, что инициализация в крестах — это совершенно ебанутое дерьмо: https://habr.com/ru/company/jugru/blog/469465/ (на ГК это обсуждалось, кстати).
              int i1;                 //undefined value
              int i2 = 42;            //note: inits with 42
              int i3(42);             //inits with 42
              int i4 = int();         //inits with 42  // gost: автор пиздит, здесь 0
              int i5{42};             //inits with 42
              int i6 = {42};          //inits with 42
              int i7{};               //inits with 0
              int i8 = {};            //inits with 0
              auto i9 = 42;           //inits with 42
              auto i10{42};           //C++11: std::initializer_list<int>, C++14: int
              auto i11 = {42};        //inits std::initializer_list<int> with 42
              auto i12 = int{42};     //inits int with 42
              int i13();              //declares a function
              int i14(7, 9);          //compile-time error
              int i15 = (7, 9);       //OK, inits int with 9 (comma operator)
              int i16 = int(7, 9);    //compile-time error
              int i17(7, 9);          //compile-time error
              auto i18 = (7, 9);      //OK, inits int with 9 (comma operator)
              auto i19 = int(7, 9);   //compile-time error


              Поэтому «A a = A{};» и «A a; a = A{};» — это две совершенно разные вещи. В первом никаких лишних объектов не создаётся, для «a» просто вызывается дефолтный коньструктор. Во втором — сначала «a» инициализируется дефолтным коньструктором, потом создаётся временный объект типа A, для «a» вызывается оператор копирующего присваивания («A & operator=(const A&)», а если бы был определён перемещающий оператор присваивания «A & operator=(A &&)», то вызвался бы он), после чего временный объект уничтожается.
              Ответить
              • Плохая подборка кстати. Разницу между i5{42} и i3(42) не позволяет прочувствовать.
                uint8_t i3(100394); // inits wth 42
                uint8_t i5{100394}; // compilation error
                Ответить
                • >148
                  лол

                  {100394} это лист из одного значения, а (100394) это вызов конструктора, хотя в случае int это наверное явная инициализация невлезающим в него числом?
                  Ответить
                  • Ну да, в случае с list initialization запрещены касты с потерей данных. А в случае с direct initialization и copy initialization - нет.
                    Ответить
                    • Если подумать, то наверное это логично.

                      Ты можешь получить снаружи 100394, и знать, что тебе оттуда нужен байт, а в остальных байтах там мусор. Ну вот ты и получаешь из него свой байт.

                      А вот почему это не работает со списком я не полнимаю.
                      Ответить
                      • > А вот почему это не работает со списком я не полнимаю.
                        Просто в новом Стандарте решили, что это слишком bug-prone, но как всегда сделали неконсистентное говно.
                        Ответить
                        • а, то-есть старое поведение оставили для совместимости со старым кодом?
                          Ответить
                  • З.Ы. Можешь почитать раздел про инициализацию на cppreference. Только пивом запасись для начала. А то там можно навечно зависнуть в рекурсии между страничками про разные типы инициализации.
                    Ответить
                    • Я не крестовичок же. Но из того, что я тут слышу от вас про кресты, у меня складывается впечатление, что многие вещи в них логичны, если задуматься.

                      А вот например в JS нет.
                      Почему числа идут раньше строк в объекте?
                      Ответить
                      • > многие вещи в них логичны

                        Они логичны, да. Насколько может быть логичной спека, в которую больше 20 лет пытаются прикручивать новые фичи не распидорасив старый код.

                        Я больше часа курил стандарт и пытался понять какого хрена инициализация структуры со () работает а со {} не конпеляется и какое вообще правило сейчас работает.
                        Ответить
                • макака разгадал 42
                  100394 кончается на 0010 1010, то-есть 42, верно?
                  Ответить
                • prog.cc: In function 'int main()':
                  prog.cc:8:16: warning: unsigned conversion from 'int' to 'uint8_t' {aka 'unsigned char'} changes value from '100394' to '42' [-Woverflow]
                      8 |     uint8_t i3(100394); // inits wth 42
                        |                ^~~~~~
                  prog.cc:9:22: error: narrowing conversion of '100394' from 'int' to 'uint8_t' {aka 'unsigned char'} [-Wnarrowing]
                      9 |     uint8_t i5{100394}; // compilation error
                        |

                  Какой багор )))
                  Ответить
    • OlegUP это новый сёма?
      Ответить
      • Опровергаю. Сёма думать не умеет, в отличие от.
        Ответить
      • Да нет. Чувак просто изучает кресты, и удивляется количеству граблей. Вполне нормально, все так делали
        Ответить
        • пошел в историю, кажется, спутал с кем-то, кто мапу не мог написать
          Ответить
          • Я писал мапу (unordered_multimap), но вроде об этом сюда не писал.
            Так и не оттестил её на потокобезопасность.
            Ответить

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