Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Tutorial

Boost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 42. Boost.Variant

BoostC++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Tutorial

Basic Usage

Дискриминируемый профсоюзный контейнер на некоторых типах определяется путем мгновенных boost::variant шаблон класса с желаемыми типами. Эти типы называются привязанными типами и подчиняются требованиям концепции BoundedType. Любое количество ограниченных типов может быть указано, вплоть до определенного предела реализации (см. BOOST_VARIANT_LIMIT_TYPES.

Например, следующий объявляет дискриминируемый профсоюзный контейнер на int и std::string:

boost::variant< int, std::string > v;

По умолчанию variant по умолчанию устанавливает свой первый ограниченный тип, поэтому v изначально содержит int(0). Если это не желаемо, или если первый ограниченный тип не является структурированным по умолчанию, a variant может быть построен непосредственно от любого значения, конвертируемого в один из его ограниченных типов. Точно так же вариант может быть присвоен любое значение конвертируемое к одному из его ограниченных типов, как показано в следующем:

v = "hello";

Теперь v содержит std::string, равный "hello". Мы можем продемонстрировать это поток v для стандартного вывода:

std::cout << v << std::endl;

Обычно, однако, мы хотели бы сделать больше с содержанием variant, чем потоковой. Таким образом, нам нужен какой-то способ получить доступ к содержащейся стоимости. Есть два способа достичь этого: apply_visitor, который является самым безопасным и очень мощным, и get, который иногда удобнее использовать.

Например, предположим, что мы хотели концентрировать строку, содержащуюся в v. С восстановление стоимости на get, это может быть достигнуто довольно просто, как показано в следующем:

std::string& str = boost::get<std::string>(v);
str += " world! ";

По желанию std::string, содержащийся в v, теперь равен "hello world! ". Опять же, мы можем продемонстрировать это, потоковая передача v для стандартного вывода:

std::cout << v << std::endl;

Хотя использование get является вполне приемлемым в этом тривиальном примере, get обычно страдает от нескольких значительных недостатков. Например, если бы мы написали функцию, принимающую variant, мы бы не знали, содержит ли пройденный variant int или std::string. Если бы мы настаивали на продолжении использования get, нам нужно было бы запросить variant для его содержащегося типа. Следующая функция, которая «удвояет» содержание данного вариант, демонстрирует такой подход:

void times_two( boost::variant< int, std::string > & operand )
{
    if ( int* pi = boost::get<int>( &operand ) )
        *pi *= 2;
    else if ( std::string* pstr = boost::get<std::string>( &operand ) )
        *pstr += *pstr;
}

Тем не менее, такой код довольно хрупкий, и без тщательного внимания, скорее всего, приведет к внедрению тонких логических ошибок, обнаруживаемых только в рабочее время. Например, рассмотрим, хотим ли мы расширить times_two для работы на variant с дополнительными связанными типами. В частности, std::complex<ДП> в комплект. Очевидно, нам нужно хотя бы изменить декларацию функций:

void times_two( boost::variant< int, std::string, std::complex<double> > & operand )
{
    // as above...?
}

Конечно, необходимы дополнительные изменения, поскольку в настоящее время, если пройденный вариант фактически содержит значение std::complex, times_two будет молча возвращаться - без каких-либо желаемых побочных эффектов и без какой-либо ошибки. В этом случае исправление очевидно. Но в более сложных программах может потребоваться значительное время, чтобы определить и обнаружить ошибку в первую очередь.

Таким образом, использование в реальном мире variant, как правило, требует более надежного механизма доступа, чем get. По этой причине вариант поддерживает проверенное время компиляции визитация через apply_visitor. Посещение требует, чтобы программист явно обрабатывал (или игнорировал) каждый ограниченный тип. Неспособность сделать это приводит к ошибке компиляции времени.

Посещение вариант требует объекта посетителя. Ниже показана одна такая реализация посетителя, реализующего поведение, идентичное times_two:

class times_two_visitor
    : public boost::static_visitor<>
{
public:
    void operator()(int & i) const
    {
        i *= 2;
    }
    void operator()(std::string & str) const
    {
        str += str;
    }
};

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

boost::apply_visitor( times_two_visitor(), v );

Как и ожидалось, содержание v сейчас является std::string равным "hello world! Привет, мир! ". (На этот раз мы пропустим проверку.)

В дополнение к повышенной надежности, посещение предоставляет еще одно важное преимущество над get: способность писать общие посетители. Например, следующий посетитель «удвоит» содержание any вариант (при условии его ограниченных типов каждый оператор поддержки+=):

class times_two_generic
    : public boost::static_visitor<>
{
public:
    template <typename T>
    void operator()( T & operand ) const
    {
        operand += operand;
    }
};

Опять же, apply_visitor устанавливает колеса в движении:

boost::apply_visitor( times_two_generic(), v );

Хотя первоначальные затраты на установку посещения могут превысить затраты, требуемые для get, преимущества быстро становятся значительными. Прежде чем завершить этот раздел, мы должны изучить одно последнее преимущество посещения с apply_visitor: задержка посещения. А именно, доступна специальная форма apply_visitor, которая не сразу применяет данного посетителя к любому variant, а скорее возвращает объект функции, который работает на любом variant, заданном ему. Это поведение особенно полезно при работе на последовательности вариант тип, как показано ниже:

std::vector< boost::variant<int, std::string> > vec;
vec.push_back( 21 );
vec.push_back( "hello " );
times_two_generic visitor;
std::for_each(
      vec.begin(), vec.end()
   , boost::apply_visitor(visitor)
   );

Advanced Topics

В этом разделе обсуждаются несколько особенностей библиотеки, часто необходимых для расширенного использования variant. В отличие от приведенного выше раздела, каждая функция, представленная ниже, в значительной степени независима от других. Соответственно, этот раздел не обязательно должен читаться линейно или полностью.

Preprocessor macros

В то время как вариатический список параметров шаблона variant значительно упрощает использование для конкретных мгновенных сообщений шаблона, это значительно усложняет использование для генерических мгновенных сообщений. Например, в то время как сразу ясно, как можно написать функцию, принимающую конкретную вариантную мгновенную передачу, скажем вариант, менее ясно, как можно написать функцию, принимающую любой заданный вариант.

Из-за отсутствия поддержки подлинных вариатических списках параметров шаблона в стандарте C++98, необходим препроцессор. В то время как препроцессорная библиотека обеспечивает общее и мощное решение, необходимость повторения BOOST_VARIANT_LIMIT_TYPES без необходимости загромождает простой код. Поэтому для общих вариантов использования эта библиотека предоставляет свой собственный макрос BOOST_VARIANT_ENUM_PARAMS.

Этот макрос упрощает для пользователя процесс объявления типов variant в шаблонах функций или явной частичной специализации шаблонов класса, как показано в следующем:

// general cases
template <typename T> void some_func(const T &);
template <typename T> class some_class;
// function template overload
template <BOOST_VARIANT_ENUM_PARAMS(typename T)>
void some_func(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> &);
// explicit partial specialization
template <BOOST_VARIANT_ENUM_PARAMS(typename T)>
class some_class< boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> >;

Using a type sequence to specify bounded types

В то время как это удобно для типичных применений, список параметров variant классного шаблона ограничивает в двух существенных размерах. Во-первых, из-за отсутствия поддержки подлинных вариатических списков параметров шаблона на C++ количество параметров должно быть ограничено определенным максимумом реализации (а именно, BOOST_VARIANT_LIMIT_TYPES). Во-вторых, характер списков параметров в целом делает манипулирование списками в компиляцию-время чрезмерно сложным.

Чтобы решить эти проблемы, make_variant_over< Последовательность > выставляет variant, чьи ограниченные типы являются элементами Sequence (где Sequence является любым типом, отвечающим требованиям MPL Sequence). Например,

typedef mpl::vector< std::string > types_initial;
typedef mpl::push_front< types_initial, int >::type types;
boost::make_variant_over< types >::type v1;

поведение эквивалентно

boost::variant< int, std::string > v2;

Портируемость: К сожалению, из-за стандартных вопросов соответствия в нескольких компиляторах make_variant_over не является универсально доступным. На этих компиляторах библиотека указывает на отсутствие поддержки синтаксиса через определение предпроцессорного символа BOOST_VARIANT_NO_TYPE_SEQUENCE_SUPPORT.

Recursive variant types

Рекурсивные типы облегчают построение сложной семантики из простого синтаксиса. Например, почти каждый программист знаком с каноническим определением реализации связанного списка, чье простое определение позволяет последовательности неограниченной длины:

template <typename T>
struct list_node
{
    T data;
    list_node * next;
};

Характер вариант как шаблон общего класса, к сожалению, исключает простое построение рекурсивных вариант Типы. Рассмотрим следующую попытку построить структуру для простых математических выражений:

struct add;
struct sub;
template <typename OpTag> struct binary_op;
typedef boost::variant<
      int
    , binary_op<add>
    , binary_op<sub>
    > expression;
template <typename OpTag>
struct binary_op
{
    expression left;  // variant instantiated here...
    expression right;
    binary_op( const expression & lhs, const expression & rhs )
        : left(lhs), right(rhs)
    {
    }
}; // ...but binary_op not complete until here!

В то время как хорошо намерения, вышеупомянутый подход не будет компилироваться, потому что binary_op все еще является неполным, когда variant тип expression мгновенный. Кроме того, подход страдает от более значительного логического недостатка: даже если бы синтаксис C++ был другим так, что приведенный выше пример мог бы быть сделан для «работы», выражение должно быть бесконечного размера, что явно невозможно.

Чтобы преодолеть эти трудности, вариант включает в себя специальную поддержку boost::recursive_wrapper шаблона класса, который нарушает круговую зависимость в основе этих проблем. Далее, boost::make_recursive_variant предоставляет более удобный синтаксис для объявления рекурсивных variant типов. Тютории для использования этих объектов описаны в разделе под названием “Recursive types with recursive_wrapper и раздел под названием “Recursive types with make_recursive_variant̵.

Recursive types with recursive_wrapper

Следующий пример показывает, как recursive_wrapper может быть использован для решения проблемы, представленной в разделе под названием “Recursive variant Типы”:

typedef boost::variant<
      int
    , boost::recursive_wrapper< binary_op<add> >
    , boost::recursive_wrapper< binary_op<sub> >
    > expression;

Поскольку variant предоставляет специальную поддержку recursive_wrapper, клиенты могут рассматривать результат variant, как если бы обертка не присутствовала. Это видно при реализации следующего посетителя, который рассчитывает значение выражения без ссылки на recursive_wrapper:

class calculator : public boost::static_visitor<int>
{
public:
    int operator()(int value) const
    {
        return value;
    }
    int operator()(const binary_op<add> & binary) const
    {
        return boost::apply_visitor( calculator(), binary.left )
             + boost::apply_visitor( calculator(), binary.right );
    }
    int operator()(const binary_op<sub> & binary) const
    {
        return boost::apply_visitor( calculator(), binary.left )
             - boost::apply_visitor( calculator(), binary.right );
    }
};

Наконец, мы можем продемонстрировать выражение в действии:

void f()
{
    // result = ((7-3)+8) = 12
    expression result(
        binary_op<add>(
            binary_op<sub>(7,3)
          , 8
          )
      );
    assert( boost::apply_visitor(calculator(),result) == 12 );
}

Последовательность: boost::recursive_wrapper не имеет пустого состояния, что делает его конструктор движения не очень оптимальным. Рассмотрим использование std::unique_ptr или другого безопасного указателя для лучшей производительности на C++11 совместимых компиляторах.

Recursive types with make_recursive_variant

Для некоторых применений рекурсивных вариантных типов пользователь может пожертвовать полной гибкостью использования recursive_wrapper с variant для следующего удобного синтаксиса:

typedef boost::make_recursive_variant<
      int
    , std::vector< boost::recursive_variant_ >
    >::type int_tree_t;

Использование результата вариант Тип, как и ожидалось:

std::vector< int_tree_t > subresult;
subresult.push_back(3);
subresult.push_back(5);
std::vector< int_tree_t > result;
result.push_back(1);
result.push_back(subresult);
result.push_back(7);
int_tree_t var(result);

Чтобы быть ясным, можно представить результатный контент var как (1 (3 5) 7).

Наконец, обратите внимание, что последовательность типа может быть использована для указания ограниченных типов рекурсивных вариант через использование boost::make_recursive_variant_over, чья семантика равна make_variant_over (который описан в разделе под названием “Использование последовательности типа для указания ограниченных типов”).

Портируемость: К сожалению, из-за стандартных вопросов соответствия в нескольких компиляторах make_recursive_variant не пользуется универсальной поддержкой. На этих компиляторах библиотека указывает на отсутствие поддержки через определение предпроцессорного символа BOOST_VARIANT_NO_FULL_RECURSIVE_VARIANT_SUPPORT. Таким образом, если не работать с высокосоответствующими компиляторами, максимальная переносимость будет достигнута за счет использования recursive_wrapper, как описано в разделе под названием “Recursive types with recursive_wrapper.

Binary visitation

Как показывает вышеупомянутый учебник, посещение является мощным механизмом для манипулирования контентом variant. Бинарные посещения далее расширяет силу и гибкость посещения, позволяя одновременно посещать содержание двух разных объектов вариант.

Примечательно, что эта функция требует, чтобы бинарные посетители были несовместимы с объектами посетителей, обсуждаемыми в учебнике выше, поскольку они должны работать на двух аргументах. Ниже показана реализация бинарных посетителей:

class are_strict_equals
    : public boost::static_visitor<bool>
{
public:
    template <typename T, typename U>
    bool operator()( const T &, const U & ) const
    {
        return false; // cannot compare different types
    }
    template <typename T>
    bool operator()( const T & lhs, const T & rhs ) const
    {
        return lhs == rhs;
    }
};

Как и ожидалось, посетитель применяется к двум аргументам variant с помощью apply_visitor:

boost::variant< int, std::string > v1( "hello" );
boost::variant< double, std::string > v2( "hello" );
assert( boost::apply_visitor(are_strict_equals(), v1, v2) );
boost::variant< int, const char * > v3( "hello" );
assert( !boost::apply_visitor(are_strict_equals(), v1, v3) );

Наконец, мы должны отметить, что объект функции вернулся из «задержанной» формы apply_visitor, как показано ниже:

typedef boost::variant<double, std::string> my_variant;
std::vector< my_variant > seq1;
seq1.push_back("pi is close to ");
seq1.push_back(3.14);
std::list< my_variant > seq2;
seq2.push_back("pi is close to ");
seq2.push_back(3.14);
are_strict_equals visitor;
assert( std::equal(
      seq1.begin(), seq1.end(), seq2.begin()
    , boost::apply_visitor( visitor )
    ) );

Multi visitation

Многократное посещение расширяет силу и гибкость посещения, позволяя одновременно посещать содержание трех и более различных объектов вариант. Обратите внимание, что заголовок для нескольких посетителей должен быть включен отдельно.

Примечательно, что эта функция требует, чтобы много посетителей несовместимы с объектами посетителей, обсуждаемыми в учебнике выше, поскольку они должны работать на том же потоке аргументов, которые были переданы apply_visitor. Ниже показана реализация мультипосетителя по трем параметрам:

#include <boost/variant/multivisitors.hpp>
typedef boost::variant<int, double, bool> bool_like_t;
typedef boost::variant<int, double> arithmetics_t;
struct if_visitor: public boost::static_visitor<arithmetics_t> {
    template <class T1, class T2>
    arithmetics_t operator()(bool b, T1 v1, T2 v2) const {
        if (b) {
            return v1;
        } else {
            return v2;
        }
    }
};

Как и ожидалось, посетитель применяется к трем аргументам variant с помощью apply_visitor:

bool_like_t v0(true), v1(1), v2(2.0);
assert(
    boost::apply_visitor(if_visitor(), v0, v1, v2)
    ==
    arithmetics_t(1)
);

Наконец, мы должны отметить, что многократное посещение не поддерживает «задержанную» форму apply_visitor, если BOOST_VARIANT_DO_NOT_USE_VARIADIC_TEMPLATES определено.


PrevUpHomeNext

Статья Tutorial раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 42. Boost.Variant может быть полезна для разработчиков на c++ и boost.




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.



:: Главная :: Chapter 42. Boost.Variant ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 17:43:17/0.017253875732422/1