- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 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;
}
OlegUP 03.07.2020 13:32 # 0
OlegUP 03.07.2020 13:41 # 0
gost 03.07.2020 13:48 # 0
Если всё же хочется поиграть со стрелковым оружием возле ноги, то как-то так:
OlegUP 03.07.2020 13:52 # 0
Именно поэтому я за C++.
OlegUP 03.07.2020 14:01 # 0
gost 03.07.2020 13:43 # 0
Нет, в этом и смысл крестового union'а.
Главное правило работы с union: не используй union. Серьёзно, в нём настолько много неопределённой хуйни, что, если ты не выучил Стандарт от корки до корки, то обязательно выстрелишь себе в ногу.
Реальный пример: https://wandbox.org/permlink/kL6PSZnyJRIrvQl7. В union'е из поста вместо данных хранится какая-то хуйня.
OlegUP 03.07.2020 14:04 # 0
bormand 03.07.2020 14:05 # 0
Лол, никак. Она просто не скомпилится если там что-то надо будет вызывать.
3.14159265 03.07.2020 17:15 # 0
Все рекламируют «новую, крутую фичу» в «Новом Стандарте™», но редко кто до конца продумывает как она будет взаимодействовать со всеми остальными конструктами языка, существовавшими ранее.
В случае современных языков вроде С++ и ECMAScript количество кобенаций разных фич растёт экспоненциально.
В Сишке с юнионами проблем такого рода не было, потому что union изначально был в дизайне языка.
А когда к спине рукав пришивают, тут уж извините.
jojaxon 03.07.2020 17:29 # 0
Удобно же - манипулятор туда и жопу можно чесать)))
nABuAH 03.07.2020 17:33 # 0
bormand 03.07.2020 14:13 # 0
Добавь ещё немного логов в конструктор и деструктор юниона и всё поймёшь.
OlegUP 03.07.2020 14:45 # +1
Ну вот тут описывается, как правильно его использовать.
bormand 03.07.2020 14:59 # +1
Ну да, есть же std::variant, который вполне понимает что в нём лежит и зовёт нужные конструкторы и деструкторы. Впизду union.
gost 03.07.2020 17:00 # 0
guest8 03.07.2020 17:03 # −999
gost 03.07.2020 17:05 # 0
Опровергаю. Ситуации, когда тип значения никак нельзя узнать в компайл-тайме, существуют.
guest8 03.07.2020 17:10 # −999
gost 03.07.2020 17:16 # 0
В полностью статическом языке без «union», «variant» и прочих «void *» конпелятор тоже знает, что, куда и когда пишется. А в случае с недодинамической типизацией программист делится с конпелятором этим знанием: либо явно («tagged union»), либо бульмень неявно (std::variant).
guest8 03.07.2020 17:25 # −999
gost 03.07.2020 17:30 # +1
guest8 03.07.2020 17:38 # −999
gost 03.07.2020 17:42 # 0
Опять же, просто, безопасно и никаких UB.
guest8 03.07.2020 17:52 # −999
bormand 03.07.2020 17:56 # 0
nemyx 03.07.2020 17:59 # 0
guest8 03.07.2020 18:01 # −999
guest8 03.07.2020 18:00 # −999
gost 03.07.2020 18:00 # 0
А что касается Питуха, то экономия на спичках может быть оправдана только тогда, когда ты создаёшь буквально миллиарды этих самых питухов. И если уж такое произошло — сделай класс «Курятник», засунь в него variant<vector<float>, vector<int>> и получи надёжную и безопасную замену юнионоговну с оверхедом в восемь байт всего. Либо вообще лучше продумай рахитектуру и избавься от необходимости в этом динамоговне.
3.14159265 03.07.2020 18:01 # 0
Но это можно всякими сазтами решить.
gost 03.07.2020 18:08 # 0
3.14159265 03.07.2020 18:13 # 0
И тогда компилер лучше питумизировал именно union с bit fields, а не ручную питушню.
Так что union — царская штука. Он быстр и опасен.
jojaxon 03.07.2020 18:04 # 0
3.14159265 03.07.2020 18:06 # 0
Нужен: disjoint union (A|B)
А обычное объединение можно заменить геттерами и внутренними кастами.
То есть всем плевать как хранятся те или иные данные, если геттеры отдают что надо.
gost 03.07.2020 18:07 # 0
MAKAKA 03.07.2020 17:07 # 0
Ну типа ты явно меняешь активного члена посредством ручного вызова десктрутора и placement new.
А если ты потрогал неактивного члена, то это UB.
Если вдуматься, то все логично.
Или нет?
bormand 03.07.2020 17:12 # 0
Если оставил член активным и свалил - UB.
MAKAKA 03.07.2020 17:13 # 0
bormand 03.07.2020 17:14 # 0
MAKAKA 03.07.2020 17:19 # 0
guest8 03.07.2020 17:13 # −999
3.14159265 03.07.2020 17:12 # 0
Там нет никаких «деструкторов» и «rtti».
MAKAKA 03.07.2020 17:13 # 0
А в C++ он стал сложным именно из за деструкторов и конструкторов.
Кстати, если у меня дефолтные конструктор и деструктор которые ничего не делают, то эт же ничем не отличается от сишечки, не?
bormand 03.07.2020 17:17 # 0
И если ты в него что-то сложнее сишной структуры положишь, то конпелятор откажется это конпелять. И тебе надо будет объявлять конструктор и деструктор вручную и управлять всем вручную.
MAKAKA 03.07.2020 17:19 # +1
В сишечке никакой код "автоматически" не вызывается, и потому все намного проще.
А как только ты привез в этот мир конструкторы и деструкторы, то у тебя сразу появился миллион вопросов.
gost 03.07.2020 17:22 # 0
На самом деле проблема не в «RAII», а в попытке усидеть на джвух стульях: иметь статически типизированный язык с возможностью в одной и той же пельменной хранить значения разных типов, определяемых в рантайме.
MAKAKA 03.07.2020 17:25 # 0
Конкретно тут проблема и в этом тоже, но в целом необходимость вызывать деструкторы делает язык сложнее.
Всякие конструкторы копирования, например.
То-есть в сишечке я четко понимаю, что вот тут есть данные, и а вот тут есть код.
В С++ (вообще в мире ООП) данные и код объеденены вместе, и манипуляция с данными приводит к вызову кода, или НЕ приводит (как в этом случае)
gost 03.07.2020 17:35 # 0
Не соглашусь. Если у тебя возникла необходимость вызывать деструктор вручную, значит, ты что-то делаешь сильно не так.
Собственно, мне на ум приходят только две ситуации, когда это необходимо делать: в обсуждаемом union-е с нетривиальными членами и при использовании «placement new». Первый вореант в 99% случаях можно заменить на std::variant и не ебать себе мозги, второй обычно означает какие-то очень хардкорные оптимизации, которые либо предварительные и не нужны вообще, либо которые можно завернуть в три слоя абсракций и забыть про них как про страшный сон.
MAKAKA 03.07.2020 17:41 # 0
Когда из области видимости выходит любой сишный объект, то происходит ничего. Когда выходит С++ный объект, то вызывается код. Хотя ты его явно не вызвал.
Когда я передаю в функцию какой-то сишный объект, он туда тупо копируется (ну кроме несчастных массивов). Когда я делаю это с С++ объектом, то вызывается копирующий коснтруктор. Хотя я его явно не вызвал.
В целом это делает код сложнее, и приводит к таким вот ситуациям, когда placement new и деструктор надо вызывать явно.
С другой стороны я согласен, что конкретно тут дело в ненужном байтоёбстве.
То-есть мы спустились на такой низкий уровень, что абстракции старые уже не работают, и потому нужно делать все вручную.
Но в сишечке такой проблемы бы не возникло. Там все всегда делается одинаковым образом: вручную, и потому все просто. Хотя и бойлерплейтно.
gost 03.07.2020 17:48 # 0
Более того, «RAII» делает это детерминированно: ты всегда можешь точно сказать, когда тот или иной объект сконьструируется и когда он разрушится.
А уж сколько боли в сишечке доставляет необходимость выделить и освободить сразу несколько ресурсов, причём ещё учесть корректную обработку ошибок…
MAKAKA 03.07.2020 17:55 # 0
С одной стороны у тебя много чего "просто работает правильно" без ручного пирдолинга.
С другой стороны система в целом становится сложнее, и если такая абстракция протекает, то приходится эту абстракцию понимать. Программист на С++ должен в целом больше абстракций понимать, чем программист на си.
RAII вот такая как раз абстракция, и вот тут она протекла.
В сишке такой абстракции нет, и протекать нечему.
gost 03.07.2020 18:04 # 0
Ну тут безусловно, кресты в целом я не оправдываю, просто указываю, что «RAII» — это хорошая идея, одна из немногих, которые в крестах сделаны хорошо.
> В сишке такой абстракции нет, и протекать нечему.
Ну да, в сишке тебе в 100% случаях надо ручками закрывать все ресурсы и чистить вилкой утечки памяти в любом бульмень крупном проекте. В крестах такой ручной труд требуется в 0.01% случаях (и это само по себе признак, что, скорее всего, у тебя в архитектуре проблемы), всё остальное за тебя сделает конпелятор.
gost 03.07.2020 17:20 # 0
Кстати, самое смешное, что деструктор союза никак не может стандартными средствами узнать, что в этом союзе хранится. Для этого придётся либо оборачивать союз в структуру, либо использовать совсем уж неадекватные вещи вроде глобальных мап «адрес_союза->тип».
Нахуя вообще сделали возможность создания деструктора союза — загадка.
bormand 03.07.2020 17:24 # 0
Ну почему. Если у меня в юнионе все типы одинаково начинаются*, то я могу обращаться к общим полям через любой из типов. И если в этих общих полях есть какая-то инфа, по которой я могу определить тип - я могу запилить деструктор.
* емнип, там довольно длинное определение в стандарте.
gost 03.07.2020 17:12 # 0
Тут проблема. Это вполне мог быть не я, а какой-то внешний кот. И чтобы узнать, какой именно член был потроган — нужно или городить костыли вроде упомянутых выше «tagged union», или не ебать себе мозги и использовать уже написанный, хороший и безопасный std::variant.
MAKAKA 03.07.2020 17:15 # 0
Просто сказал, что в целом это логично.
У тебя есть один стул, и на него нужно усадить двух питухов.
Когда ты сажаешь второго питуха туда, ты должен первого явно уничтожить, а второго явно усадить.
gost 03.07.2020 17:18 # 0
jojaxon 03.07.2020 17:32 # +1
nemyx 03.07.2020 14:15 # 0
Вот тут уже́ смешно. Поскольку a и b — члены одного union'а, то операция b = B {}; затирает значение, хранящееся в a.
Если сделать a и b не значениями, а указателями, тут вообще будет утечка.
bormand 03.07.2020 14:16 # +2
З.Ы. Ну и кстати обращение к b когда активно a - тоже UB.
gost 03.07.2020 14:25 # +1
Вот на это:
Конпелятор честно отвечает:
А если убрать иницализацию b, то всё скомпилится, но деструктор ~A() не будет вызван вообще: https://wandbox.org/permlink/iDC9LcIUfbxuHugG — потому что компилятор и правда не может знать, что именно хранится в union. Именно поэтому я против «union».
UPD: Из-за UB, видимо, и происходит хрень с мгновенным деконструированием оригинального кода.
Вообще, если в скомпилированной крестовой программе происходит какая-то совершенно неадекватная хуйня, то с очень большой вероятностью это признак того, что где-то в коде притаилось UB.
gost 03.07.2020 14:32 # 0
bormand 03.07.2020 14:36 # 0
Написав деструктор ты перешёл на ручное управление. Теперь ты отвечаешь за члены союза ;)
З.Ы. Если бы автор не экономил на логах и расставил их вокруг всех строчек в конструкторе, то всё было бы очевидно.
OlegUP 03.07.2020 18:15 # 0
Да.
https://wandbox.org/permlink/dvcC9Ah2PLvNO17O
gost 03.07.2020 18:26 # +3
Поэтому «A a = A{};» и «A a; a = A{};» — это две совершенно разные вещи. В первом никаких лишних объектов не создаётся, для «a» просто вызывается дефолтный коньструктор. Во втором — сначала «a» инициализируется дефолтным коньструктором, потом создаётся временный объект типа A, для «a» вызывается оператор копирующего присваивания («A & operator=(const A&)», а если бы был определён перемещающий оператор присваивания «A & operator=(A &&)», то вызвался бы он), после чего временный объект уничтожается.
bormand 03.07.2020 19:59 # +1
MAKAKA 03.07.2020 20:05 # 0
лол
{100394} это лист из одного значения, а (100394) это вызов конструктора, хотя в случае int это наверное явная инициализация невлезающим в него числом?
bormand 03.07.2020 20:18 # 0
MAKAKA 03.07.2020 20:22 # 0
Ты можешь получить снаружи 100394, и знать, что тебе оттуда нужен байт, а в остальных байтах там мусор. Ну вот ты и получаешь из него свой байт.
А вот почему это не работает со списком я не полнимаю.
gost 03.07.2020 20:24 # 0
Просто в новом Стандарте решили, что это слишком bug-prone, но как всегда сделали неконсистентное говно.
MAKAKA 03.07.2020 20:25 # 0
gost 03.07.2020 20:28 # 0
nemyx 03.07.2020 20:35 # 0
bormand 03.07.2020 20:19 # +1
MAKAKA 03.07.2020 23:29 # 0
А вот например в JS нет.
Почему числа идут раньше строк в объекте?
bormand 03.07.2020 23:38 # 0
Они логичны, да. Насколько может быть логичной спека, в которую больше 20 лет пытаются прикручивать новые фичи не распидорасив старый код.
Я больше часа курил стандарт и пытался понять какого хрена инициализация структуры со () работает а со {} не конпеляется и какое вообще правило сейчас работает.
MAKAKA 03.07.2020 20:11 # 0
100394 кончается на 0010 1010, то-есть 42, верно?
gost 03.07.2020 20:14 # 0
Какой багор )))
bormand 03.07.2020 20:16 # +1
Fike 03.07.2020 15:41 # 0
gost 03.07.2020 17:00 # 0
MAKAKA 03.07.2020 17:10 # 0
Fike 03.07.2020 19:04 # 0
OlegUP 04.07.2020 10:04 # 0
Так и не оттестил её на потокобезопасность.