Нашли или выдавили из себя код, который нельзя назвать нормальным,
на который без улыбки не взглянешь?
Не торопитесь его удалять или рефакторить, — запостите его на
говнокод.ру, посмеёмся вместе!
> Поэтому пользуясь union'ом, членами которого будут структуры с указателями на кучу как быть?
Если всё же хочется поиграть со стрелковым оружием возле ноги, то как-то так:
struct X {};
struct Y {};
enum UnionTag { TAG_X, TAG_Y };
struct XYUnion {
UnionTag tag;
union {
X x;
Y y;
};
};
> Получается информация о том, какой сейчас объект активен в union где-то хранится.
Нет, в этом и смысл крестового union'а.
Главное правило работы с union: не используй union. Серьёзно, в нём настолько много неопределённой хуйни, что, если ты не выучил Стандарт от корки до корки, то обязательно выстрелишь себе в ногу.
Реальный пример: https://wandbox.org/permlink/kL6PSZnyJRIrvQl7. В union'е из поста вместо данных хранится какая-то хуйня.
Проблема в неконсистетности при расширениях языков.
Все рекламируют «новую, крутую фичу» в «Новом Стандарте™», но редко кто до конца продумывает как она будет взаимодействовать со всеми остальными конструктами языка, существовавшими ранее.
В случае современных языков вроде С++ и ECMAScript количество кобенаций разных фич растёт экспоненциально.
В Сишке с юнионами проблем такого рода не было, потому что union изначально был в дизайне языка.
> std::variant
Для анскильных питушков.
> который вполне понимает
Понимать может только программист. Если он это понимает, то тратит на это лишнюю память.
Не понял тебя. Как это противоречит тому, что я напиисал? Программист пишет код, только он может знать чо куда пишет, и чо откуда может читать (и при каких условиях).
> Программист пишет код, только он может знать чо куда пишет, и чо откуда может читать (и при каких условиях).
В полностью статическом языке без «union», «variant» и прочих «void *» конпелятор тоже знает, что, куда и когда пишется. А в случае с недодинамической типизацией программист делится с конпелятором этим знанием: либо явно («tagged union»), либо бульмень неявно (std::variant).
Ну ладно, ладно, если без шутеечек, то суть в том, что использование union-а для экономии памяти — это в абсолютном большинстве случаев ненужная предварительная оптимизация, которая приводит только к говну в коде и куче потенциальных UB. Так делать не нужно.
А что касается Питуха, то экономия на спичках может быть оправдана только тогда, когда ты создаёшь буквально миллиарды этих самых питухов. И если уж такое произошло — сделай класс «Курятник», засунь в него variant<vector<float>, vector<int>> и получи надёжную и безопасную замену юнионоговну с оверхедом в восемь байт всего. Либо вообще лучше продумай рахитектуру и избавься от необходимости в этом динамоговне.
Ага. Но в целом, я когда-то замерял пирфоманс union с bit fields и рукоблудногописного сдвигового байтоёбства.
И тогда компилер лучше питумизировал именно union с bit fields, а не ручную питушню.
А обычное объединение можно заменить геттерами и внутренними кастами.
То есть всем плевать как хранятся те или иные данные, если геттеры отдают что надо.
Да он ничем от сишного в общем-то не отличается. В этом и проблема.
И если ты в него что-то сложнее сишной структуры положишь, то конпелятор откажется это конпелять. И тебе надо будет объявлять конструктор и деструктор вручную и управлять всем вручную.
В сишечке никакой код "автоматически" не вызывается, и потому все намного проще.
А как только ты привез в этот мир конструкторы и деструкторы, то у тебя сразу появился миллион вопросов.
Возможно, вы имели в виду «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(),
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() ничего не происходит, потому что компилятор не знает, какой член надо разрушать.
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 &&)», то вызвался бы он), после чего временный объект уничтожается.
> А вот почему это не работает со списком я не полнимаю.
Просто в новом Стандарте решили, что это слишком bug-prone, но как всегда сделали неконсистентное говно.
З.Ы. Можешь почитать раздел про инициализацию на cppreference. Только пивом запасись для начала. А то там можно навечно зависнуть в рекурсии между страничками про разные типы инициализации.
Они логичны, да. Насколько может быть логичной спека, в которую больше 20 лет пытаются прикручивать новые фичи не распидорасив старый код.
Я больше часа курил стандарт и пытался понять какого хрена инициализация структуры со () работает а со {} не конпеляется и какое вообще правило сейчас работает.
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 # 0
OlegUP # 0
gost # 0 ⇈
Если всё же хочется поиграть со стрелковым оружием возле ноги, то как-то так:
OlegUP # 0 ⇈
Именно поэтому я за C++.
OlegUP # 0 ⇈
gost # 0
Нет, в этом и смысл крестового union'а.
Главное правило работы с union: не используй union. Серьёзно, в нём настолько много неопределённой хуйни, что, если ты не выучил Стандарт от корки до корки, то обязательно выстрелишь себе в ногу.
Реальный пример: https://wandbox.org/permlink/kL6PSZnyJRIrvQl7. В union'е из поста вместо данных хранится какая-то хуйня.
OlegUP # 0 ⇈
bormand # 0 ⇈
Лол, никак. Она просто не скомпилится если там что-то надо будет вызывать.
3.14159265 # 0 ⇈
Все рекламируют «новую, крутую фичу» в «Новом Стандарте™», но редко кто до конца продумывает как она будет взаимодействовать со всеми остальными конструктами языка, существовавшими ранее.
В случае современных языков вроде С++ и ECMAScript количество кобенаций разных фич растёт экспоненциально.
В Сишке с юнионами проблем такого рода не было, потому что union изначально был в дизайне языка.
А когда к спине рукав пришивают, тут уж извините.
jojaxon # 0 ⇈
Удобно же - манипулятор туда и жопу можно чесать)))
nABuAH # 0 ⇈
bormand # 0 ⇈
Добавь ещё немного логов в конструктор и деструктор юниона и всё поймёшь.
OlegUP # 0 ⇈
Ну вот тут описывается, как правильно его использовать.
bormand # 0 ⇈
Ну да, есть же std::variant, который вполне понимает что в нём лежит и зовёт нужные конструкторы и деструкторы. Впизду union.
gost # 0 ⇈
guest # 0 ⇈
Для анскильных питушков.
> который вполне понимает
Понимать может только программист. Если он это понимает, то тратит на это лишнюю память.
gost # 0 ⇈
Опровергаю. Ситуации, когда тип значения никак нельзя узнать в компайл-тайме, существуют.
guest # 0 ⇈
gost # 0 ⇈
В полностью статическом языке без «union», «variant» и прочих «void *» конпелятор тоже знает, что, куда и когда пишется. А в случае с недодинамической типизацией программист делится с конпелятором этим знанием: либо явно («tagged union»), либо бульмень неявно (std::variant).
guest # 0 ⇈
Перепишешь на variant?
gost # 0 ⇈
guest # 0 ⇈
https://ideone.com/IIrrgL
gost # 0 ⇈
Опять же, просто, безопасно и никаких UB.
guest # 0 ⇈
Азаза, анскильный питушок в треде!
bormand # 0 ⇈
nemyx # 0 ⇈
guest # 0 ⇈
guest # 0 ⇈
gost # 0 ⇈
А что касается Питуха, то экономия на спичках может быть оправдана только тогда, когда ты создаёшь буквально миллиарды этих самых питухов. И если уж такое произошло — сделай класс «Курятник», засунь в него variant<vector<float>, vector<int>> и получи надёжную и безопасную замену юнионоговну с оверхедом в восемь байт всего. Либо вообще лучше продумай рахитектуру и избавься от необходимости в этом динамоговне.
3.14159265 # 0 ⇈
Но это можно всякими сазтами решить.
gost # 0 ⇈
3.14159265 # 0 ⇈
И тогда компилер лучше питумизировал именно union с bit fields, а не ручную питушню.
Так что union — царская штука. Он быстр и опасен.
jojaxon # 0 ⇈
3.14159265 # 0 ⇈
Нужен: disjoint union (A|B)
А обычное объединение можно заменить геттерами и внутренними кастами.
То есть всем плевать как хранятся те или иные данные, если геттеры отдают что надо.
gost # 0 ⇈
MAKAKA # 0 ⇈
Ну типа ты явно меняешь активного члена посредством ручного вызова десктрутора и placement new.
А если ты потрогал неактивного члена, то это UB.
Если вдуматься, то все логично.
Или нет?
bormand # 0 ⇈
Если оставил член активным и свалил - UB.
MAKAKA # 0 ⇈
bormand # 0 ⇈
MAKAKA # 0 ⇈
guest # 0 ⇈
3.14159265 # 0 ⇈
Там нет никаких «деструкторов» и «rtti».
MAKAKA # 0 ⇈
А в C++ он стал сложным именно из за деструкторов и конструкторов.
Кстати, если у меня дефолтные конструктор и деструктор которые ничего не делают, то эт же ничем не отличается от сишечки, не?
bormand # 0 ⇈
И если ты в него что-то сложнее сишной структуры положишь, то конпелятор откажется это конпелять. И тебе надо будет объявлять конструктор и деструктор вручную и управлять всем вручную.
MAKAKA # 0 ⇈
В сишечке никакой код "автоматически" не вызывается, и потому все намного проще.
А как только ты привез в этот мир конструкторы и деструкторы, то у тебя сразу появился миллион вопросов.
gost # 0 ⇈
На самом деле проблема не в «RAII», а в попытке усидеть на джвух стульях: иметь статически типизированный язык с возможностью в одной и той же пельменной хранить значения разных типов, определяемых в рантайме.
MAKAKA # 0 ⇈
Конкретно тут проблема и в этом тоже, но в целом необходимость вызывать деструкторы делает язык сложнее.
Всякие конструкторы копирования, например.
То-есть в сишечке я четко понимаю, что вот тут есть данные, и а вот тут есть код.
В С++ (вообще в мире ООП) данные и код объеденены вместе, и манипуляция с данными приводит к вызову кода, или НЕ приводит (как в этом случае)
gost # 0 ⇈
Не соглашусь. Если у тебя возникла необходимость вызывать деструктор вручную, значит, ты что-то делаешь сильно не так.
Собственно, мне на ум приходят только две ситуации, когда это необходимо делать: в обсуждаемом union-е с нетривиальными членами и при использовании «placement new». Первый вореант в 99% случаях можно заменить на std::variant и не ебать себе мозги, второй обычно означает какие-то очень хардкорные оптимизации, которые либо предварительные и не нужны вообще, либо которые можно завернуть в три слоя абсракций и забыть про них как про страшный сон.
MAKAKA # 0 ⇈
Когда из области видимости выходит любой сишный объект, то происходит ничего. Когда выходит С++ный объект, то вызывается код. Хотя ты его явно не вызвал.
Когда я передаю в функцию какой-то сишный объект, он туда тупо копируется (ну кроме несчастных массивов). Когда я делаю это с С++ объектом, то вызывается копирующий коснтруктор. Хотя я его явно не вызвал.
В целом это делает код сложнее, и приводит к таким вот ситуациям, когда placement new и деструктор надо вызывать явно.
С другой стороны я согласен, что конкретно тут дело в ненужном байтоёбстве.
То-есть мы спустились на такой низкий уровень, что абстракции старые уже не работают, и потому нужно делать все вручную.
Но в сишечке такой проблемы бы не возникло. Там все всегда делается одинаковым образом: вручную, и потому все просто. Хотя и бойлерплейтно.
gost # 0 ⇈
Более того, «RAII» делает это детерминированно: ты всегда можешь точно сказать, когда тот или иной объект сконьструируется и когда он разрушится.
А уж сколько боли в сишечке доставляет необходимость выделить и освободить сразу несколько ресурсов, причём ещё учесть корректную обработку ошибок…
MAKAKA # 0 ⇈
С одной стороны у тебя много чего "просто работает правильно" без ручного пирдолинга.
С другой стороны система в целом становится сложнее, и если такая абстракция протекает, то приходится эту абстракцию понимать. Программист на С++ должен в целом больше абстракций понимать, чем программист на си.
RAII вот такая как раз абстракция, и вот тут она протекла.
В сишке такой абстракции нет, и протекать нечему.
gost # 0 ⇈
Ну тут безусловно, кресты в целом я не оправдываю, просто указываю, что «RAII» — это хорошая идея, одна из немногих, которые в крестах сделаны хорошо.
> В сишке такой абстракции нет, и протекать нечему.
Ну да, в сишке тебе в 100% случаях надо ручками закрывать все ресурсы и чистить вилкой утечки памяти в любом бульмень крупном проекте. В крестах такой ручной труд требуется в 0.01% случаях (и это само по себе признак, что, скорее всего, у тебя в архитектуре проблемы), всё остальное за тебя сделает конпелятор.
gost # 0 ⇈
Кстати, самое смешное, что деструктор союза никак не может стандартными средствами узнать, что в этом союзе хранится. Для этого придётся либо оборачивать союз в структуру, либо использовать совсем уж неадекватные вещи вроде глобальных мап «адрес_союза->тип».
Нахуя вообще сделали возможность создания деструктора союза — загадка.
bormand # 0 ⇈
Ну почему. Если у меня в юнионе все типы одинаково начинаются*, то я могу обращаться к общим полям через любой из типов. И если в этих общих полях есть какая-то инфа, по которой я могу определить тип - я могу запилить деструктор.
* емнип, там довольно длинное определение в стандарте.
gost # 0 ⇈
Тут проблема. Это вполне мог быть не я, а какой-то внешний кот. И чтобы узнать, какой именно член был потроган — нужно или городить костыли вроде упомянутых выше «tagged union», или не ебать себе мозги и использовать уже написанный, хороший и безопасный std::variant.
MAKAKA # 0 ⇈
Просто сказал, что в целом это логично.
У тебя есть один стул, и на него нужно усадить двух питухов.
Когда ты сажаешь второго питуха туда, ты должен первого явно уничтожить, а второго явно усадить.
gost # 0 ⇈
jojaxon # 0 ⇈
nemyx # 0
Вот тут уже́ смешно. Поскольку a и b — члены одного union'а, то операция b = B {}; затирает значение, хранящееся в a.
Если сделать a и b не значениями, а указателями, тут вообще будет утечка.
bormand # 0 ⇈
З.Ы. Ну и кстати обращение к b когда активно a - тоже UB.
gost # 0 ⇈
Вот на это:
Конпелятор честно отвечает:
А если убрать иницализацию b, то всё скомпилится, но деструктор ~A() не будет вызван вообще: https://wandbox.org/permlink/iDC9LcIUfbxuHugG — потому что компилятор и правда не может знать, что именно хранится в union. Именно поэтому я против «union».
UPD: Из-за UB, видимо, и происходит хрень с мгновенным деконструированием оригинального кода.
Вообще, если в скомпилированной крестовой программе происходит какая-то совершенно неадекватная хуйня, то с очень большой вероятностью это признак того, что где-то в коде притаилось UB.
gost # 0 ⇈
bormand # 0 ⇈
Написав деструктор ты перешёл на ручное управление. Теперь ты отвечаешь за члены союза 😉
З.Ы. Если бы автор не экономил на логах и расставил их вокруг всех строчек в конструкторе, то всё было бы очевидно.
OlegUP # 0 ⇈
Да.
https://wandbox.org/permlink/dvcC9Ah2PLvNO17O
gost # 0 ⇈
Поэтому «A a = A{};» и «A a; a = A{};» — это две совершенно разные вещи. В первом никаких лишних объектов не создаётся, для «a» просто вызывается дефолтный коньструктор. Во втором — сначала «a» инициализируется дефолтным коньструктором, потом создаётся временный объект типа A, для «a» вызывается оператор копирующего присваивания («A & operator=(const A&)», а если бы был определён перемещающий оператор присваивания «A & operator=(A &&)», то вызвался бы он), после чего временный объект уничтожается.
bormand # 0 ⇈
MAKAKA # 0 ⇈
лол
{100394} это лист из одного значения, а (100394) это вызов конструктора, хотя в случае int это наверное явная инициализация невлезающим в него числом?
bormand # 0 ⇈
MAKAKA # 0 ⇈
Ты можешь получить снаружи 100394, и знать, что тебе оттуда нужен байт, а в остальных байтах там мусор. Ну вот ты и получаешь из него свой байт.
А вот почему это не работает со списком я не полнимаю.
gost # 0 ⇈
Просто в новом Стандарте решили, что это слишком bug-prone, но как всегда сделали неконсистентное говно.
MAKAKA # 0 ⇈
gost # 0 ⇈
nemyx # 0 ⇈
bormand # 0 ⇈
MAKAKA # 0 ⇈
А вот например в JS нет.
Почему числа идут раньше строк в объекте?
bormand # 0 ⇈
Они логичны, да. Насколько может быть логичной спека, в которую больше 20 лет пытаются прикручивать новые фичи не распидорасив старый код.
Я больше часа курил стандарт и пытался понять какого хрена инициализация структуры со () работает а со {} не конпеляется и какое вообще правило сейчас работает.
MAKAKA # 0 ⇈
100394 кончается на 0010 1010, то-есть 42, верно?
gost # 0 ⇈
Какой багор )))
bormand # 0 ⇈
Fike # 0
gost # 0 ⇈
MAKAKA # 0 ⇈
Fike # 0 ⇈
OlegUP # 0 ⇈
Так и не оттестил её на потокобезопасность.