Обратите внимание: any_cast
any_cast(x)
. Именно это мы и сделали, когда получали доступ к объектам типа string
или list
в коде данного раздела. std::bad_any_cas
Хранение разных типов с применением std::variant
В языке С++ для создания типов можно использовать не только примитивы struct
class
. Если нужно выразить, что какие-то переменные могут содержать значения типа А
либо значения типа В
(или C
, или любого другого), то на помощь придут объединения. Проблема с объединениями заключается в том, что они не могут сказать, для хранения каких типов были инициализированы.Рассмотрим следующий код:
union U {
int a;
char *b;
float c;
};
void func(U u) { std::cout << u.b << '\n'; }
Допустим, мы вызовем функцию func
a
. Тогда ничто не помешает нам получить доступ к нему так, как если бы оно было инициализировано способом, позволяющим хранить в нем указатель на строку в члене b
. Из подобного кода могут появиться самые разнообразные ошибки. Прежде чем мы поместим в наше объединение вспомогательную переменную, которая скажет нам, для чего оно было инициализировано, можем воспользоваться типом std::variant
, появившимся в C++17.Тип variant
union
. Он не использует кучу, поэтому настолько же эффективно задействует память и время, как и решение, основанное на объединениях, так что нам нет нужды реализовывать его самостоятельно. Тип может хранить все что угодно, кроме ссылок массивов или объектов типа void
.В этом разделе мы создадим программу, которая задействует тип variant
Как это делается
В этом примере мы реализуем программу, которая уже знакома с типами cat
dog
и сохраняет смешанный список экземпляров обоих типов, не используя полиморфизм.1. Сначала включим все необходимые заголовочные файлы и объявим об использовании пространства имен std
#include
#include
#include
#include
#include
using namespace std;
2. Далее реализуем два класса, имеющих схожий инструментарий, но не связанных друг с другом, что отличает их от классов, которые, скажем, наследуют от одного интерфейса или похожих интерфейсов. Первый класс — это класс cat
class cat {
string name;
public:
cat(string n) : name{n} {}
void meow() const {
cout << name << " says Meow!\n";
}
};
3. Второй класс — это класс dog
dog
, конечно, может сказать не class dog {
string name;
public:
dog(string n) : name{n} {}
void woof() const {
cout << name << " says Woof!\n";
}
};
4. Теперь можно определить тип animal
std::variant
. По сути, он работает как старое доброе объединение, но имеет все дополнительные средства, предоставленные типом variant
:using animal = variant
5. Прежде чем писать основную программу, нужно реализовать два вспомогательных элемента. Одним из них является предикат animal
is_type(...)
или is_type(...)
, можно определить, какого типа данные содержатся в экземпляре типа animal
. Реализация просто вызывает функцию holds_alternative
, которая, по сути, является обобщенной функцией-предикатом для типа variant
:template
bool is_type(const animal &a) {
return holds_alternative
}
6. Вторым вспомогательным элементом является структура, которая ведет себя как объект функции. Это двойной объект функции, поскольку он дважды реализует оператор ()
dog
, вторая же принимает экземпляры типа cat
. Для этих типов она просто вызывает функции woof
или meow
:struct animal_voice
{
void operator()(const dog &d) const { d.woof(); }
void operator()(const cat &c) const { c.meow(); }
};
7. Воспользуемся результатами нашего труда. Сначала определим список переменных типа animal
cat
и dog
:int main()
{