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

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

Шрифт:

-
+

Интервал:

-
+
1 ... 83 84 85 86 87 88 89 90 91 ... 121
Перейти на страницу:
конструктору типа any достаточно информации о том, что мы собираемся создать. Второй параметр, {1,2,3}, — просто список инициализации, который будет передан в int_list, будучи встроенным в переменную типа any с целью создания объекта. Это способ избежать ненужного копирования или перемещения.

  print_anything(any(in_place_type_t<int_list>{}, {1, 2, 3}));

}

11. Компиляция и запуск программы дадут следующие результаты, они полностью соответствуют нашим ожиданиям:

$ ./any

Nothing.

It's a string: "abc"

It's an integer: 123

It's a list: 1, 2, 3,

It's a list: 1, 2, 3,

Как это работает

Тип std::any в чем-то похож на тип std::optional — он поддерживает метод has_value(), который говорит, содержит ли экземпляр значение. Но, помимо этого, он может содержать все что угодно, вследствие чего с ним работать немного сложнее, нежели с типом optional.

Прежде чем получать доступ к содержимому переменной типа any, нужно определить, какого типа хранящееся в ней значение, а затем преобразовать данные к этому типу.

Определить тип значения можно с помощью следующего сравнения: x.type() == typeid(T). Если оно возвращает результат true, то можно использовать преобразование any_cast, чтобы получить содержимое.

Обратите внимание: any_cast<T>(x) возвращает копию внутреннего значения. Если нужно получить ссылку, чтобы избежать копирования сложных объектов, то следует использовать конструкцию any_cast<T&>(x). Именно это мы и сделали, когда получали доступ к объектам типа string или list<int> в коде данного раздела.

 

 Если мы преобразуем экземпляр к неправильному типу, будет сгенерировано исключение std::bad_any_cast.

Хранение разных типов с применением 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 <iostream>

#include <variant>

#include <list>

#include <string>

#include <algorithm>

using namespace std;

2. Далее реализуем два класса, имеющих схожий инструментарий, но не связанных друг с другом, что отличает их от классов, которые, скажем, наследуют от одного интерфейса или похожих интерфейсов. Первый класс — это класс cat. Объект класса cat имеет имя и может сказать «мяу» (meow):

class cat {

  string name;

public:

  cat(string n) : name{n} {}

  void meow() const {

    cout << name << " says Meow!n";

  }

};

3. Второй класс — это класс dog. Объект класса dog, конечно, может сказать не «мяу», а «гав» (woof):

class dog {

  string name;

public:

  dog(string n) : name{n} {}

  void woof() const {

    cout << name << " says Woof!n";

  }

};

4. Теперь можно определить тип animal, он будет представлять собой псевдоним типа std::variant<dog, cat>. По сути, он работает как старое доброе объединение, но имеет все дополнительные средства, предоставленные типом variant:

using animal = variant<dog, cat>;

5. Прежде чем писать основную программу, нужно реализовать два вспомогательных элемента. Одним из них является предикат animal. Вызвав is_type<cat>(...) или is_type<dog>(...), можно определить, какого типа данные содержатся в экземпляре типа animal. Реализация просто вызывает функцию holds_alternative, которая, по сути, является обобщенной функцией-предикатом для типа variant:

template <typename T>

bool is_type(const animal &a) {

  return holds_alternative<T>(a);

}

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()

{

  list<animal> l {cat{"Tuba"}, dog{"Balou"}, cat{"Bobby"}};

8. Теперь трижды выведем на экран содержимое списка, каждый раз новым способом. Один из них заключается в использовании variant::index(). Поскольку animal является псевдонимом для variant<dog, cat>, возвращаемое значение 0 означает, что переменная хранит экземпляр типа dog. Значение индекса 1 говорит о том, что это экземпляр типа cat. Здесь важен порядок типов в специализации variant. В блоке

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

Комментарии

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

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