|
boost::variantSample programs |
This program computes the total sum of numerical values of various types.
The code defines a variant
object, v1
, which can
hold a value of this set of types: short, int, float
, and
double
.
double_sum
is a visitor class: Its function-call operator,
double_sum::operator()
, accepts a single value, and adds it to the
total_
data member. The program uses the construct: "boost::apply_visitor(visitor,
variant);
" to invoke the visitor object, (ds)
on the
specified variant (v1)
.
Naturally, ds.total_
holds the total of all previously "visited"
values, and - therefore - at the bottom of main()
, its value is: 5
+ 16 + 3.11 + 15.3 = 39.41.
#include <iostream> #include "boost/variant.hpp" struct double_sum : boost::static_visitor<double> { double_sum() : total_(0.0) { } template<class X> double operator()(X x) { total_ += x; return total_; } double total_; }; int main(int, char* [] ) { double_sum ds; boost::variant<short, int, float, double> v1; v1 = short(5); boost::apply_visitor(ds, v1); // Apply ds to v1 (1st time) v1 = 16; boost::apply_visitor(ds, v1); // 2nd time v1 = 3.11f; boost::apply_visitor(ds, v1); // 3rd v1 = 15.3; double total = boost::apply_visitor(ds, v1); // 4th // Expected output: "Total = 39.41" std::cout << "Total = " << total << std::endl; return 0; }
This sample program shows how incomplete<T>
can be used to
define recursive variants
.
The code creates a small binary tree and then performs an in-order walk thru
its nodes, producing this output: 3 4 6 10 19 20 23
#include <iostream> #include "boost/variant.hpp" #include "boost/incomplete.hpp" using boost::variant; using boost::incomplete; using std::cout; using std::endl; struct non_leaf_node; // Forward declaration // Define a variant with these two types: // 1) int // 2) The (incomplete) non_leaf_node struct typedef variant<int, incomplete<non_leaf_node> > tree_node; struct non_leaf_node { non_leaf_node(const tree_node& l, int num, const tree_node& r) : left_(l), right_(r), num_(num) { } non_leaf_node(const non_leaf_node& other) : left_(other.left_), right_(other.right_), num_(other.num_) { } int num_; tree_node left_; tree_node right_; }; struct tree_printer : boost::static_visitor<void> { void operator()(int n) const { cout << n << ' '; } void operator()(const non_leaf_node& node) const { boost::apply_visitor(*this, node.left_); cout << node.num_ << ' '; boost::apply_visitor(*this, node.right_); } }; int main(int, char* [] ) { //Build a binary search tree: non_leaf_node a(3,4, 6); non_leaf_node b(19, 20, 23); non_leaf_node c(a,10, b); tree_node root(c); //Perform an in-order walk boost::apply_visitor(tree_printer(), root); return 0; }
Let's assume we need to write a program that manipulates instances of
star
and space_ship
, where each of these two classes
inherits from space_object
. The program maintains a vector
of pointers to these objects, which is used to calculate the total mass of
all star
objects:
// // 'classic' inheritance-based implementation // #include <vector> #include <algorithm> #include <iostream> struct space_object { virtual int mass() const = 0; virtual ~space_object() { } }; struct space_ship : space_object { space_ship(int m = 0) : m_(m) { } int mass() const { return m_; } int get_speed() const { return 15; } int m_; }; struct star : space_object { star(int m = 0) : m_(m) { } int mass() const { return m_; } int m_; }; struct total_mass { total_mass() : total_(0) { } void operator()(space_object* so_p) { if(dynamic_cast<star*>(so_p)) total_ += so_p->mass(); } int total_; }; int main(int, char* [] ) { typedef std::vector<space_object*> main_vec; main_vec space_objects; //fill space_objects // ... total_mass tm_job; int total = std::for_each(space_objects.begin(), space_objects.end(), tm_job).total_; std::cout << "Total mass of all stars = " << total << std::endl; //Apply delete to all pointers stored in space_objects // ... return 0; }
The are several issues worth noticing about this sample:
total_mass::operator()
is a
costly operation. Alternatively, one can define an enum
type
which will be used to correctly identify the concrete object, but this is an
error prone technique: the author must manually set the correct value for each
new concrete class. total_mass
is unsafe when new classes are introduced.
Suppose a new class, black_hole
- inherits directly from
space_object
- is added to the code. total_mass
will
silently ignore this class, possibly creating a havoc of run-time problems.
This is a major flaw from software engineering standpoint. This real-life design problem can be elegantly solved using variants. Here is the variant-based code:
// // Variant-based implementation // #include <vector> #include <algorithm> #include <iostream> #include "boost/variant.hpp" struct space_ship { space_ship(int m = 0) : m_(m) { } int mass() const { return m_; } int get_speed() const { return 15; } int m_; }; struct star { star(int m = 0) : m_(m) { } int mass() const { return m_; } int m_; }; struct total_mass : boost::static_visitor<void> { total_mass() : total_(0) { } void operator()(const star& a_star) { total_ += a_star.mass(); } //space_ship objects are ignored: void operator()(const space_ship& ) { } int total_; }; int main(int, char* [] ) { typedef boost::variant<star, space_ship> space_var; typedef std::vector<space_var> main_vec; main_vec space_objects; //fill space_objects // ... total_mass tm_job; std::for_each(space_objects.begin(), space_objects.end(), boost::apply_visitor(tm_job)); std::cout << "Total mass of all stars = " << tm_job.total_ << std::endl; return 0; }
This implementation directly addresses the three issues raised by the
non-variant implementation: (1) The space_objects
vector now holds
objects (rather than pointers), (2) dynamic_cast<>
s are
not needed at all, and - most importantly - (3) the compiler will
produce an error if total_mass
is not changed, when a new
class is introduced.
Revised 14 February 2003
© Copyright Eric Friedman and Itay Maman 2002-2003. All rights reserved.
Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. Eric Friedman and Itay Maman make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty.