📚 Hub Books: Онлайн-чтение книгРазная литератураC++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+
1 ... 84 85 86 87 88 89 90 91 92 ... 121
Перейти на страницу:
switch case мы получаем доступ к variant с помощью вызова get<T> для получения экземпляра типа cat или dog, хранящегося внутри:

  for (const animal &a : l) {

    switch (a.index()) {

    case 0:

      get<dog>(a).woof();

      break;

    case 1:

      get<cat>(a).meow();

      break;

    }

  }

  cout << "-----n";

9. Вместо того чтобы использовать численный индекс типа, можно также явно запросить каждый тип. Вызов get_if<dog> возвращает указатель на объект типа do на внутренний экземпляр типа dog. Если такого экземпляра внутри нет, то указатель равен null. Таким образом, мы можем попробовать получать разные типы до тех пор, пока не преуспеем.

  for (const animal &a : l) {

    if (const auto d (get_if<dog>(&a)); d) {

        d->woof();

    } else if (const auto c (get_if<cat>(&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<T> может быть специализирован для типов cat и dog, а затем использован в комбинации с std::count_if, чтобы получить количество экземпляров этого типа:

  cout << "There are "

       << count_if(begin(l), end(l), is_type<cat>)

       << " cats and "

       << count_if(begin(l), end(l), is_type<dog>)

       << " 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> должен хранить один экземпляр типа A, B или C. Нельзя сделать так, чтобы в экземпляре типа variant не хранился ни один экземпляр. Это значит, что тип std::variant не поддерживает возможность опциональности.

Экземпляр типа variant<A, B, C> имитирует объединение, которое может выглядеть так:

union U {

  A a;

  B b;

  C c;

};

Проблема с объединениями заключается в том, что нужно создавать собственные механизмы для определения того, экземпляром какого типа оно было инициализировано: A, B или C. Тип std::variant может сделать это за нас, не прилагая особых усилий.

В коде, показанном в этом разделе, мы использовали три разных способа работы с содержимым переменной variant.

Первый способ — применение функции index() типа variant. Для типа variant<A, B, C> она может вернуть индекс 0, если экземпляр был инициализирован переменной типа A, 1 для типа B или 2 для типа C, и т.д. для более сложных вариантов.

Следующий способ — использование функции get_if<T>. Она принимает адрес объекта типа variant и возвращает указатель типа T на его содержимое. Если тип T указан неправильно, то указатель станет нулевым. Кроме того, можно вызвать метод get<T>(x) для переменной типа variant, чтобы получить ссылку на ее содержимое, но если это не сработает, то данная функция сгенерирует исключение (перед выполнением таких преобразований можно проверить правильность типа с помощью булева предиката holds_alternative<T>(x)).

Последний способ получить доступ к значению, хранящемуся в типе variant, — применить функцию std::visit. Она принимает объект функции и экземпляр типа variant. Функция проверяет, какой тип имеет содержимое экземпляра типа variant, а затем вызывает соответствующий перегруженный оператор () объекта функции.

Именно для этих целей мы и реализовали тип animal_voice, поскольку он может быть использован в комбинации с visit и variant<dog, cat>:

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, мы защищены от утечек памяти, поскольку при разрушении объекта данный класс автоматически вызывает поддерживаемый им объект.

Уникальный указатель выражает принадлежность объекта,

1 ... 84 85 86 87 88 89 90 91 92 ... 121
Перейти на страницу:

Комментарии

Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!

Никто еще не прокомментировал. Хотите быть первым, кто выскажется?