Кресты / Говнокод #26789 Ссылка на оригинал

0

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  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 OlegUP, (Updated )

Комментарии (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 изначально был в дизайне языка.

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

        Добавь ещё немного логов в конструктор и деструктор юниона и всё поймёшь.
        Ответить
    • > не используй union

      Ну да, есть же std::variant, который вполне понимает что в нём лежит и зовёт нужные конструкторы и деструкторы. Впизду union.
      Ответить
      • > std::variant
        Для анскильных питушков.
        > который вполне понимает
        Понимать может только программист. Если он это понимает, то тратит на это лишнюю память.
        Ответить
        • > Понимать может только программист.
          Опровергаю. Ситуации, когда тип значения никак нельзя узнать в компайл-тайме, существуют.
          Ответить
          • Не понял тебя. Как это противоречит тому, что я напиисал? Программист пишет код, только он может знать чо куда пишет, и чо откуда может читать (и при каких условиях).
            Ответить
            • > Программист пишет код, только он может знать чо куда пишет, и чо откуда может читать (и при каких условиях).
              В полностью статическом языке без «union», «variant» и прочих «void *» конпелятор тоже знает, что, куда и когда пишется. А в случае с недодинамической типизацией программист делится с конпелятором этим знанием: либо явно («tagged union»), либо бульмень неявно (std::variant).
              Ответить
                • Переписал вообще без «variant» и заодно убрал возможные «UB», проверь.
                  struct Pituh {
                      int x;
                  
                      void set(int, int v)
                      {
                          x = v;
                      }
                  
                      int get(int)
                      {
                          return x;
                      }
                  };
                  static_assert(sizeof(Pituh) == 4);
                  Ответить
                  • Блядь, я же специально простой пример придумал, а ты начал искать смысл и оптимизировать. Ладно, давай так:
                    https://ideone.com/IIrrgL
                    Ответить
                    • Пожалуйста.
                      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.
                      Ответить
                      • Ты забыл дописать
                        static_assert(sizeof(Pituh) == 8);


                        Азаза, анскильный питушок в треде!
                        Ответить
                          • На самом деле зависит от кокококонкретной реализации кокококомпилятора. Где-то может быть и не 4, и не 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 старому
              Ответить
        • Звучит как очередная порнуха на порнхабе.
          Ответить
      • Именно поэтому я за Сишку.
        Там нет никаких «деструкторов» и «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://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
                      |

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

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

Где здесь C++, guest?!

    А не использовать ли нам bbcode?


    8