C/C++ Tagged/Discriminated Union
Организация и обновление метаданных объединения для программного использования и доступа
Эта статья основана на темах, представленных здесь: https://medium.com/@almtechhub/c-c-self-referential-recursive-unions-22b334493eaa
Задний план
Если вы знакомы с объединениями в C/C++, то вы, вероятно, знаете, что сами по себе объединения не являются надежными структурами данных, особенно в C++, где доступ к неустановленным свойствам является неопределенным поведением. По этой причине в компьютерных науках существует давняя концепция, известная как теговые объединения.
Проще говоря, помеченные объединения — это объединения, с которыми связан фрагмент данных, отслеживающий, какие из потенциальных свойств объединения установлены в данный момент. В C++ вы можете выбирать между структурами и классами для инкапсуляции объединения или разрабатывать собственное решение для базы данных (например, карту). Конечный вариант использования, который я имею в виду, довольно прост, поэтому я собираюсь использовать структуры.
Простой пример
#include <iostream> union vals { char ch; int nt; }; struct tagUnion { char tag; vals val; }; int main() { tagUnion tu; tu.tag = 'i'; tu.val.nt = 21; for(int i = 0; i < 5; i++) { if(tu.tag == 'i') { std::cout << tu.val.nt << std::endl; tu.tag = 'c'; tu.val.ch = 'A'; continue; } if(tu.tag == 'c') { std::cout << tu.val.ch << std::endl; tu.tag = 'i'; tu.val.ch = i; continue; } } }
В этом примере у нас есть простое объединение, которое содержит char и int. Это объединение заключено в структуру, которая имеет переменную char и экземпляр объединения. Мы используем одну букву char для обозначения каждого типа, либо i, либо c. В main мы инициализируем объединение с тегами, а затем зацикливаемся. На каждой итерации мы проверяем, какое свойство установлено, регистрируем значение, переворачиваем тег и устанавливаем связанное свойство. Результат его запуска:
21 A 1 A 3
Это ожидаемый результат, мы успешно создали объединение с тегами и использовали его простым программным способом. Теперь давайте вернемся к примеру с самореферентным союзом из прошлой статьи.
Самореферентный тегированный союз
Если мы применим уроки этой статьи к примеру рекурсивного объединения из предыдущей статьи, вы получите что-то вроде:
union test { test * next; int val; }; enum tagType { link, value }; struct taggedTest { tagType typ; test val; };
Однако, если мы используем эту настройку, когда мы обращаемся к taggestTest.val.next, мы указываем на другое объединение и не имеем нашего тега. Вместо этого нам нужно, чтобы объединение ссылалось на структуру taggedTest, поэтому мы используем предварительное объявление. Вот полный пример с простым тестом:
#include <iostream> struct taggedTest; union test { taggedTest * next; int val; }; enum tagType { link, value }; struct taggedTest { tagType typ; test val; }; int main() { taggedTest test1; taggedTest test2; taggedTest test3; taggedTest test4; taggedTest test5; test5.val.val = 2; test5.typ = value; test4.val.next = &test5; test4.typ = link; test3.val.next = &test4; test3.typ = link; test2.val.next = &test3; test2.typ = link; test1.val.next = &test2; test1.typ = link; taggedTest * tmpTT = &test1; int i = 1; while(true) { if(tmpTT->typ == link) { tmpTT = tmpTT->val.next; std::cout << "Crossing Link: " << i++ << std::endl; } else if(tmpTT->typ == value) { std::cout << tmpTT->val.val << std::endl; break; } } }
Вывод
Объединения имеют сомнительную ценность без отслеживания того, какое свойство активно. Это меньше беспокоит в C, который поддерживает каламбур типов, но в C++ доступ к неактивным свойствам является неопределенным поведением. Распространенным решением этой проблемы являются помеченные/размеченные объединения, которые в C++ достигаются с использованием классов или структур. Поскольку мой конечный вариант использования прост, я решил использовать структуры.
Теговые объединения довольно просты в реализации, но в сочетании с рекурсивными объединениями возникают дополнительные сложности. Когда нам нужны самоссылающиеся тегированные объединения, мы должны предварительно объявить нашу структуру и использовать указатель на структуру в качестве следующего типа в объединении.