dog
. Если такого экземпляра внутри нет, то указатель равен null
. Таким образом, мы можем попробовать получать разные типы до тех пор, пока не преуспеем.
for (const animal &a : l) {
if (const auto d (get_if(&a)); d) {
d->woof();
} else if (const auto c (get_if(&a)); c) {
c->meow();
}
}
cout << "-----\n";
10. Последний — и самый элегантный вариант — это
variant::visit
. Данная функция принимает объект функции и экземпляр типа variant
. Объект функции должен реализовывать разные перегруженные версии для всех вероятных типов, которые может хранить variant
. Ранее мы реализовали структуру, имеющую необходимые перегруженные версии оператора ()
, поэтому можем использовать ее здесь:
for (const animal &a : l) {
visit(animal_voice{}, a);
}
cout << "-----\n";
11. Наконец подсчитаем количество экземпляров типов
cat
и dog
в списке. Предикат is_type
может быть специализирован для типов cat
и dog
, а затем использован в комбинации с std::count_if
, чтобы получить количество экземпляров этого типа:
cout << "There are "
<< count_if(begin(l), end(l), is_type)
<< " cats and "
<< count_if(begin(l), end(l), is_type)
<< " dogs in the list.\n";
}
12. После компиляции и запуска программы на экране будет список, выведенный три раза. Затем мы увидим, что предикаты
is_type
, объединенные с count_if
, тоже работают хорошо:
$ ./variant Tuba says Meow!
Balou says Woof!
Bobby says Meow!
-----
Tuba says Meow!
Balou says Woof!
Bobby says Meow!
-----
Tuba says Meow!
Balou says Woof!
Bobby says Meow!
-----
There are 2 cats and 1 dogs in the list.
Как это работает
Тип
std::variant
похож на тип std::any
, поскольку они оба могут содержать объекты разных типов, и нужно определять во время работы программы, что именно в них хранится, прежде чем получить доступ к их содержимому.С другой стороны, тип
std::variant
отличается от std::any
тем, что мы должны объявлять, экземпляры каких типов он может хранить в виде списка шаблонных типов. Экземпляр типа std::variant
должен хранить один экземпляр типа A
, B
или C
. Нельзя сделать так, чтобы в экземпляре типа variant
не хранился ни один экземпляр. Это значит, что тип std::variant
не поддерживает возможность опциональности.Экземпляр типа
variant
имитирует объединение, которое может выглядеть так:
union U {
A a;
B b;
C c;
};
Проблема с объединениями заключается в том, что нужно создавать собственные механизмы для определения того, экземпляром какого типа оно было инициализировано:
A
, B
или C
. Тип std::variant
может сделать это за нас, не прилагая особых усилий.В коде, показанном в этом разделе, мы использовали три разных способа работы с содержимым переменной variant.
Первый способ — применение функции
index()
типа variant
. Для типа variant
она может вернуть индекс 0
, если экземпляр был инициализирован переменной типа A
, 1
для типа B
или 2
для типа C
, и т.д. для более сложных вариантов.Следующий способ — использование функции
get_if
. Она принимает адрес объекта типа variant
и возвращает указатель типа T
на его содержимое. Если тип T
указан неправильно, то указатель станет нулевым. Кроме того, можно вызвать метод get(x)
для переменной типа variant
, чтобы получить ссылку на ее содержимое, но если это не сработает, то данная функция сгенерирует исключение (перед выполнением таких преобразований можно проверить правильность типа с помощью булева предиката holds_alternative(x)
).Последний способ получить доступ к значению, хранящемуся в типе
variant
, — применить функцию std::visit
. Она принимает объект функции и экземпляр типа variant
. Функция проверяет, какой тип имеет содержимое экземпляра типа variant
, а затем вызывает соответствующий перегруженный оператор ()
объекта функции.Именно для этих целей мы и реализовали тип animal_voice, поскольку он может быть использован в комбинации с
visit
и variant
:
struct animal_voice
{
void operator()(const dog &d) const { d.woof(); }
void operator()(const cat &c) const { c.meow(); }
};
Последний описанный способ получения доступа к экземплярам, хранящимся в экземплярах типа
variant
, считается самым элегантным, поскольку в разделах кода, в которых мы получаем доступ к экземпляру, не нужно жестко кодировать возможные типы. Это позволяет проще расширять код.
Утверждение о том, что тип
variant
не может не иметь значения, было не совсем верным. Добавив тип std::monostate
в список вероятных типов, можно указать, что экземпляр не будет хранить значения.Автоматическое управление ресурсами с помощью std::unique_ptr
Начиная с C++11 в STL появились умные указатели, помогающие отслеживать динамическую память и ее использование. Даже до C++11 существовал класс
auto_ptr
, который мог управлять динамической памятью, но его было легко применить неправильно.Однако с появлением умных указателей теперь редко приходится самостоятельно использовать ключевые слова
new
и delete
, и это очень хорошо. Умные указатели — отличный пример автоматического управления памятью. Поддерживая объекты, память для которых выделяется динамически с помощью unique_ptr
, мы защищены от утечек памяти, поскольку при разрушении объекта данный класс автоматически вызывает поддерживаемый им объект.Уникальный указатель выражает принадлежность объекта, на который ссылается, и выполняет свою задачу по освобождению его памяти, если та более не используется. Этот класс может навсегда освободить нас от утечек памяти (во всяком случае вместе со своими компаньонами
shared_ptr
и weak_ptr
, но в этом примере мы концентрируемся только на unique_ptr
). Самая приятная особенность заключается в том, что он не влияет на производительность и свободное место в сравнении с кодом, содержащим необработанные указатели и предусматривающим ручное управление памятью. (О’кей, он все еще устанавливает значение внутреннего необработанного указателя на