Используете ли вы boost::variant
или одну из многих реализаций C ++ 11 с открытым исходным кодом 11 или типа варианта в ваших проектах C ++?
boost::variant
- отличная библиотека. Я создал strict_variant
, чтобы рассмотреть несколько вещей о boost::variant
, которые мне не нравились.
Версия TL; DR - это то, что в отличие от boost::variant
или std::variant
, strict_variant
никогда не выпустит исключение и не сделает динамическое распределение в усилиях поддержки типов, которые имеют бросающие движения. Версия по умолчанию просто не пройдет статическое утверждение, если это произойдет. strict_variant::easy_variant
будет выделять в этой ситуации, так что вы можете выбрать это, если хотите, и эти две версии варианта «хорошо играют» вместе. Такие вещи часто являются серьезной проблемой в проектах с требованиями в реальном времени или в встроенных устройствах, которые могут не допустить, или просто может не иметь этих функций C ++. Если вы делаете библиотеку, которая может использоваться в «обычных» проектах, которые хотят простой использования, который поступает из boost::variant
, но также может использоваться в проектах с ограничительными требованиями, и вы хотите использовать variant
типа Как часть API, strict_variant
может предложить способ сделать всех счастливыми.
Кроме того, есть некоторые проблемы в интерфейсе варианта, которые были решены, которые делают его более приятным для использования ежедневного ИМХО. (Это была на самом деле оригинальная мотивация проекта.)
Мне не нравился этот код, такой как этот, может компилироваться без каких -либо предупреждений или сообщений об ошибках:
boost::variant<std::string, int > v;
v = true ;
Обычно я бы предпочел, что мой variant
более ограничен в том, какие неявные преобразования могут произойти.
Я хотел , чтобы подобные вещи должны компилировать и делать то, что имеет смысл, даже если разрешение перегрузки будет неоднозначным.
variant< bool , long , double , std::string> v;
v = true ; // selects bool
v = 10 ; // selects long
v = 20 . 5f ; // selects double
v = " foo " ; // selects string
Я также хотел, чтобы такое поведение (то, что выбирается в таких случаях) переносимо.
(Для подобных примеров кода, где boost::variant
имеет неудачное поведение, см. «Аннотация и мотивация» в документации.)
В strict_variant
мы изменяем разрешение перегрузки в этих ситуациях, удалив некоторых кандидатов.
Например:
bool
, интегральной, плавающей запятой, указателем, характером и некоторыми другими классами.int -> long
и int -> long long
-это кандидаты, то long long
исключено.Смотрите документацию для деталей.
Мне не понравилось этот boost::variant
молча сделает резервные копии моих объектов. Например, рассмотрите эту простую программу, в которой A
и B
были определены для регистрации всех вызовов CTOR и DTOR.
int main () {
using var_t = boost::variant<A, B>;
var_t v{ A ()};
std::cout << " 1 " << std::endl;
v = B ();
std::cout << " 2 " << std::endl;
v = A ();
std::cout << " 3 " << std::endl;
}
boost::variant
создает следующий вывод:
A ()
A(A&&)
~A()
1
B()
B(B&&)
A( const A &)
~A()
B( const B &)
~A()
~B()
~B()
2
A()
A(A&&)
B( const B &)
~B()
A( const A &)
~B()
~A()
~A()
3
~A()
Это может быть довольно удивительно для некоторых программистов.
Напротив, если вы используете std::variant
или один из вариантов с «иногда пустой» семантикой, вы получаете что-то подобное (этот вывод от std::experimental::variant
)
A ()
A(A&&)
~A()
1
B()
~A()
B(B&&)
~B()
2
A()
~B()
A(A&&)
~A()
3
~A()
Это намного ближе к тому, что ожидает наивный программист, который не знает о внутренних деталях boost::variant
- единственные копии его объектов, которые существуют, - это то, что он может видеть в своем исходном коде.
Такие вещи обычно не имеют значения, но иногда, если, например, вы отлаживаете неприятную проблему повреждения памяти (возможно, есть плохой код в одном из объектов, содержащихся в варианте), тогда эти дополнительные объекты, движения и копии могут Сделайте все более сложным.
Вот что вы получаете с strict_variant
:
A ()
A(A&&)
~A()
1
B()
B(B&&)
~A()
~B()
2
A()
A(A&&)
~B()
~A()
3
~A()
Тем не менее, strict_variant
не имеет пустого состояния, и он полностью безопасен!
(Эти примеры из gcc 5.4
см. Код в example
папки.)
Суммировать различия:
std::variant
редко-пустым, всегда на основе стека. На самом деле, это пусто, когда исключение брошено. Позже он бросает разные исключения, если вы попытаетесь посетить, когда он пуст.boost::variant
никогда не бывает пустым, обычно на основе стека. Он должен сделать динамическое распределение и резервную копию всякий раз, когда может быть брошено исключение, но это освобождается сразу после того, что, если исключение на самом деле не будет выброшено.strict_variant
никогда не бывает пустым, и на основе стека точно, когда текущий тип значения не подходит. Это никогда не делает резервное движение или копирование, и никогда не бросает исключение. Каждый подход имеет свои достоинства. Я выбрал подход strict_variant
, потому что я нахожу его проще, и он избегает того, что я считаю недостатками boost::variant
и std::variant
. И, если вам удастся сделать все свои типы невозможными движения конструкцией, которую я часто нахожу, то strict_variant
дает вам оптимальную производительность, так же, как std::variant
, без пустого состояния.
Для подробного обсуждения дизайна, ознакомьтесь с документацией.
Для нежного вступления в варианты и обзор строгого варианта см. Слайды из разговора, который я выступил об этом: [PPTX] [PDF]
На страницах GitHub.
strict_variant
нацелен на стандарт C ++ 11.
Известно, что он работает с gcc >= 4.8
и clang >= 3.5
и проверяется на MSVC 2015
.
strict_variant
может использоваться как в проектах, которые требуют -fno-exceptions
и -fno-rtti
.
Строгий вариант доступен по лицензии на программное обеспечение Boost.