Много раз, во время разработки программы на C++, программисту необходимо манипулировать несколькими различными типами в единообразной манере. Действительно, C++ имеет прямую языковую поддержку для таких типов с помощью ключевого слова<union
>:
union { int i; double d; } u;
u.d = 3.14;
u.i = 3; // overwrites u.d (OK: u.d is a POD type)
Конструкция C++<union
>практически бесполезна в объектно-ориентированной среде. Конструкция вошла в язык прежде всего как средство для сохранения совместимости с C, которое поддерживает только типы POD (Plain Old Data) и поэтому не принимает типы, демонстрирующие нетривиальную конструкцию или разрушение:
union {
int i;
std::string s; // illegal: std::string is not a POD type!
} u;
Очевидно, требуется другой подход. Типичные решения характеризуются динамическим распределением объектов, которыми впоследствии манипулируют через общий базовый тип (часто виртуальный базовый классHen01) или, что более опасно,<void*
>). Объекты бетонного типа могут быть затем извлечены посредством полиморфной нисходящей конструкции (например,<dynamic_cast
>,<boost::any_cast
>и т.д.).
Однако решения такого рода очень подвержены ошибкам из-за следующего:
- Ошибки сбрасывания не могут быть обнаружены во время компиляции.Таким образом, неправильное использование нисходящих конструкций приведет к ошибкам, обнаруживаемым только во время выполнения.
- Добавление новых конкретных типов может быть проигнорировано.Если к иерархии добавляется новый конкретный тип, существующий нисходящий код будет продолжать работать как есть, полностью игнорируя новый тип. Следовательно, программист должен вручную находить и изменять код в нескольких местах, что часто приводит к ошибкам времени выполнения, которые трудно найти.
Кроме того, даже при правильной реализации эти решения, как правило, несут относительно значительный штраф за абстракцию из-за использования кучи, вызовов виртуальных функций и полиморфных нисходящих потоков.
Solution: A Motivating Example
Шаблон класса<boost::variant
>решает эти проблемы безопасным, простым и эффективным способом. Следующий пример показывает, как можно использовать класс:
#include "boost/variant.hpp"
#include <iostream>
class my_visitor : public boost::static_visitor
<int>
{
public:
int operator()(int i) const
{
return i;
}
int operator()(const std::string
& str) const
{
return str.length();
}
};
int main()
{
boost::variant
< int, std::string > u("hello world");
std::cout << u; // output: hello world
int result = boost::apply_visitor
( my_visitor(), u );
std::cout << result; // output: 11 (i.e., length of "hello world")
}