Призначення об'єднань в C і C ++


254

Раніше я зручніше використовував спілки; сьогодні я насторожився, коли прочитав цей пост і дізнався, що це код

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

насправді невизначена поведінка, тобто читання від члена профспілки, відмінне від недавно написаного, призводить до невизначеної поведінки. Якщо це не призначене використання профспілок, що таке? Чи може хтось, будь ласка, пояснити це детально?

Оновлення:

Я хотів уточнити кілька речей заднім числом.

  • Відповідь на запитання не однакова для C та C ++; моя необізнана молодша власна особа позначила це як C і C ++.
  • Переглянувши стандарт C ++ 11, я не зміг однозначно сказати, що він вимагає доступу / перевірки неактивного члена профспілки невизначений / не визначений / визначений реалізацією. Все, що я міг знайти, - § 9.5 / 1:

    Якщо об'єднання стандартного макета містить кілька структур стандартного макета, які мають спільну початкову послідовність, і якщо об'єкт цього типу об'єднання стандартного макета містить одну із структур стандартного компонування, дозволяється перевіряти загальну початкову послідовність будь-якої членів структури стандартної верстки § 9.2 / 19: Дві структури стандартного макету мають спільну початкову послідовність, якщо відповідні члени мають типи сумісних з компонуванням і будь-який з членів є бітовим полем або обидва є бітовими полями однакової ширини для послідовності однієї або більше початкових члени.

  • Перебуваючи в C, ( C99 TC3 - DR 283 і далі) це законно ( завдяки Паскалю Куоку за те, що це підняло ). Однак спроба зробити це все ж може призвести до невизначеної поведінки , якщо прочитане значення виявиться недійсним (так зване "представлення пастки") для типу, через який він читається. В іншому випадку значення читання визначається реалізацією.
  • C89 / 90 назвав це не визначеним поведінкою (додаток J), а книга K&R говорить, що його реалізація визначена. Цитата від K&R:

    Це мета об'єднання - єдиної змінної, яка може законно містити будь-який з декількох типів. [...] до тих пір, поки використання буде послідовним: отриманий тип повинен бути типом, який останнім часом зберігається. Відповідальність програміста слідкує, який тип зберігається в союзі; результати залежать від реалізації, якщо щось зберігається як один тип і витягується як інший.

  • Витяг з Stroustrup's TC ++ PL (міна акценту)

    Використання спілок може бути суттєвим для сумісності даних [...], іноді неправильно використовуваних для "перетворення типу ".

Перш за все, це запитання (назва якого залишається незмінним з мого запитання) було поставлено з наміром зрозуміти мету об'єднань І не на те, що стандарт дозволяє, наприклад, Використання успадкування для повторного використання коду, звичайно, дозволено стандартом C ++, але це не було ціллю або початковим наміром запровадити спадщину як особливість мови C ++ . Саме тому відповідь Андрія продовжує залишатися прийнятою.


11
Простіше кажучи, компіляторам дозволено вставляти прокладки між елементами в структурі. Таким чином, b, g, r,і aне може бути суміжним, і, таким чином, не відповідати макеті a uint32_t. Це додатково до питань Ендіанес, на які вказували інші.
Томас Меттьюз

8
Саме тому не слід мітити теги C і C ++. Відповіді різні, але оскільки відповідачі навіть не кажуть за тег, на який вони відповідають (вони навіть знають?), Ви отримуєте сміття.
Паскаль Куок

5
@downvoter Дякую за не пояснення, я розумію, що ти хочеш, щоб я магічно зрозумів твій захват і не повторював це в майбутньому: P
legends2k

1
Що стосується первісного наміру створити союз , майте на увазі, що C стандартні об'єднання C після дати датують на кілька років. Швидкий погляд на Unix V7 показує кілька типів перетворень через союзи.
ніндзя

3
scouring C++11's standard I couldn't conclusively say that it calls out accessing/inspecting a non-active union member is undefined [...] All I could find was §9.5/1... справді? ви цитуєте примітку про виняток , а не головну точку на початку параграфа : "У об'єднанні, максимум, один із нестатичних членів даних може бути активним у будь-який час, тобто значення, щонайменше, одного з члени нестатичних даних можуть зберігатися в об'єднанні в будь-який час ". - і вниз до p4: "Взагалі потрібно використовувати явні виклики деструктора та розміщення нових операторів, щоб змінити активного члена союзу "
underscore_d

Відповіді:


407

Мета спілок досить очевидна, але люди чомусь її дуже часто пропускають.

Мета об'єднання - збереження пам'яті , використовуючи одну і ту ж область пам'яті для зберігання різних об'єктів у різний час. Це воно.

Це як номер у готелі. У ньому живуть різні люди протягом періодів, що не перетинаються. Ці люди ніколи не зустрічаються і взагалі нічого не знають один про одного. Правильно керуючи тимчасовим розподілом номерів (тобто, переконуючись, що різні люди не призначаються одночасно до однієї кімнати), порівняно невеликий готель може забезпечити розміщення відносно великої кількості людей, що саме це готелі є для.

Саме це і робить союз. Якщо ви знаєте, що кілька об’єктів у вашій програмі містять значення з неперекритими значеннями життя, тоді ви можете "об'єднати" ці об'єкти в об'єднання і таким чином зберегти пам'ять. Так само, як у готельному номері максимум один «активний» орендар у кожний момент часу, профспілка має максимум одного «активного» члена у кожен момент програмного часу. Читати може лише "активний" член. Входячи в інший член, ви перемикаєте "активний" статус на цей інший член.

Чомусь цю первісну мету спілки "переосмислили" чимось зовсім іншим: написання одного члена спілки, а потім перевірка його через іншого члена. Цей вид реінтерпретації пам'яті (відомий також як "тип карання") не є коректним використанням об'єднань. Це, як правило, призводить до невизначеного поведінки , описаного як створення поведінки, визначеної реалізацією в C89 / 90.

EDIT: Використання об'єднань для цілей покарання (тобто написання одного члена, а потім читання іншого) було дане більш детальне визначення в одній з Технічних виправлень до стандарту C99 (див. DR # 257 та DR # 283 ). Однак майте на увазі, що формально це не захищає вас від невдалої поведінки, намагаючись прочитати уявлення про пастку.


37
+1 за те, що вона була детальною, наводила простий практичний приклад і говорила про спадщину спілок!
legends2k

6
Проблема, з якою я маю цю відповідь, полягає в тому, що більшість ОС, які я бачив, мають файли заголовків, які роблять це точно. Наприклад, я бачив це в старих (до 64-бітних) версіях для <time.h>Windows і Unix. Відхилення його як "недійсне" та "невизначене" насправді недостатньо, якщо мене попросять зрозуміти код, який працює саме таким чином.
ТЕД

31
@AndreyT "Ніколи не було законним використовувати союзи для типового покарання до недавнього часу": 2004 рік не є "зовсім недавнім", особливо зважаючи на те, що тільки C99 був спочатку незграбно сформульований, і, мабуть, зробив типовий наклад через профспілки не визначеним. Насправді, покарання за типом, хоча спілки є законними в C89, законними в C11, і це було законним в C99 весь час, хоча до 2004 року комітету було потрібно виправити невірні формулювання та подальше звільнення TC3. open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm
Паскаль Куок

6
@ legends2k Мова програмування визначена стандартною. Технічне виправлення 3 стандарту C99 чітко передбачає набір покарань у виносці 82, яку я пропоную прочитати для себе. Це не телебачення, де рок-зірок опитують та висловлюють свою думку щодо зміни клімату. Думка Stroustrup має нульовий вплив на те, що говорить стандарт C.
Паскаль Куок

6
@ legends2k " Я знаю, що думка будь-якої людини не має значення, і лише стандарт має " Думка авторів-упорядників має значення набагато більше, ніж "надзвичайно бідна)" специфікація мови ".
curiousguy

38

Ви можете використовувати спілки для створення таких структур, які містять поле, яке вказує нам, який компонент об'єднання використовується насправді:

struct VAROBJECT
{
    enum o_t { Int, Double, String } objectType;

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;

Я повністю погоджуюся, не входячи в хаос невизначеного поведінки, можливо, це найкраще призначене поведінку спілок, про які я можу придумати; але не буде зайвим простором, коли я просто використовую, скажімо, intабо char*для 10 предметів []; у такому випадку я можу фактично оголосити окремі структури для кожного типу даних замість VAROBJECT? Хіба це не зменшить безладу і використовувати менше місця?
legends2k

3
легенди: У деяких випадках ви просто не можете цього зробити. Ви використовуєте щось на зразок VAROBJECT в C в тих же випадках, коли ви використовуєте Object на Java.
Еріх Кітцмюллер

Як ви пояснюєте, структура даних мічених об'єднань є єдиним законним використанням об'єднань.
legends2k

Наведіть також приклад, як використовувати значення.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 Частина прикладу з C ++ Primer може допомогти. wandbox.org/permlink/cFSrXyG02vOSdBk2
Рік

34

Поведінка не визначена з мовної точки зору. Врахуйте, що різні платформи можуть мати різні обмеження у вирівнюванні пам'яті та витримці. Код у великій ендіанській порівняно з маленькою ендіанською машиною оновить значення в структурі по-різному. Виправлення поведінки в мові вимагало б, щоб усі реалізації використовували однакові терміни (і обмеження вирівнювання пам'яті ...), що обмежують використання.

Якщо ви використовуєте C ++ (ви використовуєте два теги) і вам дуже важливо переносимість, то ви можете просто скористатись структурою та надати сеттер, який приймає uint32_tта встановлює поля належним чином за допомогою операцій біткої маски. Те ж саме можна зробити в C з функцією.

Редагувати : Я очікував, що AProgrammer запише відповідь для голосування і закрив цю. Як зазначалося в деяких коментарях, ендіанство розглядається в інших частинах стандарту, дозволяючи кожній реалізації вирішувати, що робити, а вирівнювання та прокладка також можна обробляти по-різному. Тепер важливим моментом тут є суворі правила зшивання, на які неявно посилається AProgrammer. Компілятору дозволяється робити припущення щодо зміни (або відсутності модифікації) змінних. У випадку об'єднання компілятор може переупорядкувати вказівки та перемістити зчитування кожного компонента кольору через запис до змінної кольору.


+1 за чітку та просту відповідь! Я погоджуюсь, що для портативності метод, який ви вказали у другому параграфі, є хорошим; але чи можу я використовувати спосіб, поставлений у запитанні, якщо мій код прив’язаний до однієї архітектури (сплачуючи ціну протабельності), оскільки це економить 4 байти за кожне значення пікселя і деякий час, що зберігається при виконанні цієї функції ?
legends2k

Проблема endian не змушує стандарт оголошувати її як невизначену поведінку - reinterpret_cast має точно такі ж проблеми ендіану, але має поведінку, визначену реалізацією.
JoeG

1
@ legends2k, проблема полягає в тому, що оптимізатор може припустити, що uint32_t не змінюється, записуючи на uint8_t, і тому ви отримуєте неправильне значення, коли оптимізоване використання цього припущення ... @Joe, невизначене поведінка з'являється, як тільки ви отримаєте доступ покажчик (я знаю, є деякі винятки).
AProgrammer

1
@ legends2k / AProgrammer: Результатом reinterpret_cast є визначена реалізація. Використання повернутого вказівника не призводить до невизначеної поведінки, лише до визначеної реалізацією поведінки. Іншими словами, поведінка повинна бути консистентною та визначеною, але вона не є портативною.
JoeG

1
@ legends2k: будь-який гідний оптимізатор розпізнає побітові операції, які вибирають цілий байт і генерують код для читання / запису байта, такий же, як об'єднання, але добре визначений (і портативний). наприклад, uint8_t getRed () const {return color & 0x000000FF; } void setRed (uint8_t r) {color = (color & ~ 0x000000FF) | r; }
Бен Войгт

22

Найбільш поширене використання, яке unionя регулярно зустрічаю, - це згладжування .

Розглянемо наступне:

union Vector3f
{
  struct{ float x,y,z ; } ;
  float elts[3];
}

Що це робить? Це дозволяє отримувати чіткий, акуратний доступ Vector3f vec;учасників за будь-яким ім'ям:

vec.x=vec.y=vec.z=1.f ;

або за допомогою цілого доступу до масиву

for( int i = 0 ; i < 3 ; i++ )
  vec.elts[i]=1.f;

У деяких випадках доступ до імені - це найяскравіша річ, яку ви можете зробити. В інших випадках, особливо коли вісь обрана програмно, легше зробити доступ до осі за числовим індексом - 0 для х, 1 для у і 2 для z.


3
Це також називається, про type-punningщо також йдеться у питанні. Також подібний приклад показує приклад у питанні.
legends2k

4
Це не тип покарання. У моєму прикладі типи відповідають , тому немає "каламбура", це просто згладжування.
bobobobo

3
Так, але все ж, з точки зору абсолютного мовного стандарту, учасник, записаний та прочитаний, відрізняється, що не визначено, як зазначено в питанні.
legends2k

3
Я сподіваюся, що майбутній стандарт зафіксує, щоб цей конкретний випадок був дозволений за правилом "загальної початкової підпорядкованості". Однак масиви не беруть участі в цьому правилі за чинним формулюванням.
Бен Войгт

3
@curiousguy: Очевидно, що немає вимоги, щоб члени структури розміщувались без довільних накладок. Якщо тести коду на розміщення членів структури або розмір структури, код повинен працювати, якщо доступ здійснюється безпосередньо через союз, але чітке читання Стандарту вказуватиме на те, що прийняття адреси об'єднання або члена структури дає результат, який не можна використовувати як вказівник власного типу, але спочатку його потрібно перетворити назад у вказівник на тип, що вкладається, або тип символу. Будь-який віддалено працездатний компілятор розширить мову, зробивши більшу роботу, ніж ...
supercat

10

Як ви кажете, це суворо невизначена поведінка, хоча це буде "працювати" на багатьох платформах. Справжня причина використання спілок - це створення варіантів записів.

union A {
   int i;
   double d;
};

A a[10];    // records in "a" can be either ints or doubles 
a[0].i = 42;
a[1].d = 1.23;

Звичайно, вам також потрібен якийсь дискримінатор, щоб сказати, який варіант насправді містить. І зауважте, що в C ++ профспілки мало використовуються, оскільки вони можуть містити лише типи POD - фактично ті, що не мають конструкторів та деструкторів.


Ви використовували його таким чином (як у запитанні) ?? :)
legends2k

Це трохи педантично, але я не дуже приймаю "варіанти записів". Тобто я впевнений, що вони мали на увазі, але якщо вони були пріоритетними, то чому б не надати їх? "Надайте будівельний блок, тому що це може бути корисно і для побудови інших речей", просто здається інтуїтивно більш ймовірним. Особливо дано принаймні ще одне додаток, яке, мабуть, було на увазі - регістри вводу / виводу, нанесені на пам'ять, де регістри вводу та виведення (при цьому перекриваються) - це окремі об'єкти із власними іменами, типами тощо
Steve314,

@ Stev314 Якби це було на увазі, вони могли б зробити це не визначеною поведінкою.

@Neil: +1 вперше скаже про фактичне використання, не зачіпаючи невизначене поведінку. Я думаю, що вони могли б зробити його реалізацією визначеною як інші операції виправлення типу (reinterpret_cast тощо). Але, як я запитав, ти це використовував для типового покарання?
legends2k

@Neil - приклад регістру, відображений на пам'ять, не визначений, звичайний ендіан / і т.д. відхиляється і надається "мінливий" прапор. Запис на адресу в цій моделі не посилається на той самий реєстр, як на читання тієї самої адреси. Тому немає жодного питання "що ти читаєш назад", оскільки ти не читаєш назад - незалежно від того, який вихід ти написав на цю адресу, коли читаєш, ти просто читаєш незалежний ввід. Єдине питання - переконатися, що ви читаєте вхідну частину об'єднання та записуєте вихідну сторону. Був поширеним у вбудованих речах - напевно, все ще є.
Steve314

8

В C це був приємний спосіб втілити щось на зразок варіанту.

enum possibleTypes{
  eInt,
  eDouble,
  eChar
}


struct Value{

    union Value {
      int iVal_;
      double dval;
      char cVal;
    } value_;
    possibleTypes discriminator_;
} 

switch(val.discriminator_)
{
  case eInt: val.value_.iVal_; break;

У часи пам’яті Літлле ця структура використовує менше пам’яті, ніж структура, яка має весь член.

До речі, як це забезпечує C

    typedef struct {
      unsigned int mantissa_low:32;      //mantissa
      unsigned int mantissa_high:20;
      unsigned int exponent:11;         //exponent
      unsigned int sign:1;
    } realVal;

для доступу до бітових значень.


Хоча обидва ваші приклади чудово визначені у стандарті; але, ей, використовуючи бітові поля, ви впевнені, що знімали невідповідний код, чи не так?
legends2k

Ні, це не так. Наскільки мені відомо, її широко підтримують.
Тотонга

1
Підтримка компілятора не перекладається на портативний. Книга C : C (тим самим C ++) не дає гарантії впорядкованості полів у машинних словах, тому якщо ви їх використовуєте з останньої причини, програма не лише буде непереносною, але й залежатиме від компілятора.
legends2k

5

Хоча це суворо невизначена поведінка, на практиці це працюватиме з майже будь-яким компілятором. Це така широко застосовувана парадигма, що будь-який поважаючий себе компілятор повинен буде зробити «правильну справу» у таких випадках. Це, безумовно, слід віддати перевагу над типовим покаранням, яке може генерувати зламаний код з деякими компіляторами.


2
Хіба не існує ендіанської проблеми? Відносно легкий виправлення порівняно з "невизначеним", але варто враховувати деякі проекти, якщо так.
Steve314

5

У C ++ Boost Variant реалізує безпечну версію об'єднання, розроблену для того, щоб максимально запобігти невизначеній поведінці.

Його продуктивність ідентична enum + unionконструкції (стек також виділений тощо), але він використовує шаблон типів замість enum:)


5

Поведінка може бути невизначеною, але це просто означає, що немає "стандарту". Усі пристойні компілятори пропонують #pragmas контролювати упаковку та вирівнювання, але можуть мати різні за замовчуванням. Значення за замовчуванням також змінюватимуться в залежності від використовуваних налаштувань оптимізації.

Також спілки не просто для економії місця. Вони можуть допомогти сучасним компіляторам з типовим каруванням. Якщо ви reinterpret_cast<>все, компілятор не може робити припущення щодо того, що ви робите. Можливо, доведеться викинути те, що він знає про ваш тип, і почати заново (змушує записувати назад у пам'ять, що в наші дні дуже неефективно порівняно з тактовою частотою процесора).


4

Технічно це не визначено, але насправді більшість (усіх?) Компіляторів трактують це точно так само, як і використання reinterpret_castодного типу до іншого, в результаті якого визначено реалізацію. Я б не втрачав сон над вашим поточним кодом.


" реінтерпретація_каска від одного типу до іншого, результатом якого є реалізація. " Ні, це не так. Впровадження не повинно визначати це, і більшість не визначає. Крім того, якою була б дозволена реалізація поведінки перекидання деякого випадкового значення вказівник?
curiousguy

4

Для ще одного прикладу фактичного використання спілок рамки CORBA серіалізують об'єкти, використовуючи підхід з теговим об'єднанням. Усі визначені користувачем класи є членами одного (величезного) об'єднання, і цілочисельний ідентифікатор повідомляє демаршеру, як інтерпретувати об'єднання.


4

Інші згадували архітектурні відмінності (мало - великий ендіан).

Я читаю проблему, що оскільки пам'ять для змінних поділяється, то, записуючи до однієї, інші змінюються, і залежно від їх типу значення може бути безглуздим.

напр. союз {float f; int i; } х;

Писати на xi було б безглуздо, якби потім читати з xf - хіба що це те, що ти задумав, щоб подивитися на знакові, експонентні або мантійські компоненти поплавця.

Я думаю, що існує також проблема вирівнювання: Якщо деякі змінні повинні бути вирівняні словом, то, можливо, ви не отримаєте очікуваного результату.

напр. союз {char c [4]; int i; } х;

Якщо, гіпотетично, на якійсь машині значок потрібно було вирівняти словом, тоді c [0] і c [1] поділяють сховище з i, а не c [2] і c [3].


Байт, який повинен бути вирівняний словом? В цьому немає сенсу. Байти не мають вимоги вирівнювання, за визначенням.
curiousguy

Так, я, мабуть, мав би використати кращий приклад. Дякую.
philcolbourn

@curiousguy: Є багато випадків, коли можна побажати, щоб масиви байтів були вирівняними у слові. Якщо в одному є багато масивів, наприклад, 1024 байти, і вони часто бажають копіювати один в інший, їх вирівнювання може в багатьох системах подвоїти швидкість a memcpy()з одного на інший. Деякі системи можуть спекулятивно вирівняти char[]розподіли, які відбуваються поза структурами / об'єднаннями з цієї та інших причин. У наявному прикладі припущення, яке iперекриє всі елементи елементів c[], не є портативним, але це тому, що на це немає жодної гарантії sizeof(int)==4.
supercat

4

У мові С, як це було зафіксовано в 1974 році, всі члени структури поділили загальний простір імен, і значення "ptr-> член" було визначено як додавання переміщення члена до "ptr" та отримання доступу до отриманої адреси за допомогою типу члена. Ця конструкція дозволила використовувати один і той же ptr з іменами членів, взяті з різних визначень структури, але з однаковим зміщенням; програмісти використовували цю здатність для різних цілей.

Коли членам структури було призначено власні простори імен, неможливо було оголосити двох членів структури з однаковим переміщенням. Додавання об'єднань до мови дозволило досягти тієї самої семантики, яка була доступна в попередніх версіях мови (хоча неможливість експортувати імена в контекст, що вкладається, можливо, все ще потребувала використання знаходження / заміни для заміни foo-> члена в foo-> type1.member). Важливим було не стільки те, що люди, які додали спілки, мають на увазі певне цільове використання, а скоріше, щоб вони забезпечували засоби, завдяки яким програмісти, які покладалися на попередню семантику, з будь-якою метою , все-таки зможуть досягти досягнення та ж семантика, навіть якщо для цього їм довелося використовувати інший синтаксис.


Оцініть урок історії, однак зі стандартом, що визначає таке і таке, як невизначене, що не було у минулу епоху C, коли книга K&R була єдиним «стандартом», треба бути впевненим у тому, щоб не використовувати її з будь-якою метою та ввести землю УБ.
legends2k

2
@ legends2k: Коли був написаний Стандарт, більшість реалізацій C ставилися до профспілок так само, і таке лікування було корисним. Однак, деякі з них цього не зробили, і автори Стандарту не хотіли б маркувати будь-які існуючі реалізації як "невідповідні". Натомість вони подумали, що якщо реалізаторам не знадобиться Стандарт, щоб сказати їм щось робити (про що свідчить той факт, що вони вже робили це ), залишаючи це не визначеним чи невизначеним, просто збереже статус-кво . Поняття, що воно повинно робити речі менш визначеними, ніж вони були до написання стандарту ...
supercat

2
... здається набагато новішим нововведенням. Особливо сумно з усього цього полягає в тому, що якщо автори-компілятори, орієнтовані на додаткові додатки, повинні розібратися, як додати корисні директиви щодо оптимізації до мови, більшість компіляторів, реалізованих у 90-х роках, замість того, щоб витримати функції та гарантії, які підтримували лише " "90% реалізацій, результатом буде мова, яка могла б працювати краще і надійніше, ніж гіперсучасний C.
supercat

2

Ви можете використовувати aa Union з двох основних причин:

  1. Зручний спосіб отримати доступ до одних і тих же даних різними способами, як у вашому прикладі
  2. Спосіб економії місця, коли є різні члени даних, з яких лише один може бути "активним"

1 Це насправді більше хак-стилю C для скорочення короткого коду письма, виходячи з того, що ви знаєте, як працює архітектура пам'яті цільової системи. Як уже було сказано, ви можете нормально піти від цього, якщо фактично не орієнтуєтесь на безліч різних платформ. Я вважаю, що деякі компілятори можуть також дозволити вам використовувати директиви щодо упаковки (я знаю, що вони роблять для structs)?

Хороший приклад 2. можна знайти у типу VARIANT, що широко використовується в COM.


2

Як згадували інші, об'єднання, поєднані з перерахуваннями і загорнуті в структури, можуть бути використані для реалізації позначених об'єднань. Одне практичне використання - це реалізація Rust's Result<T, E>, яка спочатку реалізована за допомогою чистого enum(Rust може містити додаткові дані у варіантах перерахування). Ось приклад C ++:

template <typename T, typename E> struct Result {
    public:
    enum class Success : uint8_t { Ok, Err };
    Result(T val) {
        m_success = Success::Ok;
        m_value.ok = val;
    }
    Result(E val) {
        m_success = Success::Err;
        m_value.err = val;
    }
    inline bool operator==(const Result& other) {
        return other.m_success == this->m_success;
    }
    inline bool operator!=(const Result& other) {
        return other.m_success != this->m_success;
    }
    inline T expect(const char* errorMsg) {
        if (m_success == Success::Err) throw errorMsg;
        else return m_value.ok;
    }
    inline bool is_ok() {
        return m_success == Success::Ok;
    }
    inline bool is_err() {
        return m_success == Success::Err;
    }
    inline const T* ok() {
        if (is_ok()) return m_value.ok;
        else return nullptr;
    }
    inline const T* err() {
        if (is_err()) return m_value.err;
        else return nullptr;
    }

    // Other methods from https://doc.rust-lang.org/std/result/enum.Result.html

    private:
    Success m_success;
    union _val_t { T ok; E err; } m_value;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.