Lately at work I’ve been playing around with C++ and the Boost libraries; specifically I have been using Boost.Any, Boost.Filesystem, Boost.ProgramOptions, Boost.Python, and last but not least Boost smart pointers.
These libraries, in conjunction with some of my own macros and setup have made for a very nice transition back to C++ from C#. The awesome things about C# are the class libraries and the syntactic sugar (foreach, lambdas, anonymous methods). Similar things can be done with C++ and Boost (Boost.Foreach, etc).
I’m going to talk about the way I’ve setup my command line options parsing, to give you an idea as to how I am using Boost.
First off, I have a static class called OptionsParser which looks like this:
class OptionsParser
{
public:
static bool isParsed(void);
static void parseOptions(const std::vector<std::string>& options);
static void registerOptions(const std::string& owner, const boost::program_options::options_description& options);
static void printAllOptions(std::ostream& output);
static const std::vector<std::string> getUnrecognizedOptions(void) const;
template<class T>
static T getValue(const std::string& option, T def)
{
T result = def;
if(!isParsed())
throw std::exception("You must call parseOptions before trying to retrieve values");
if(_variables_map.count(option))
result = _variables_map[option].as<T>();
return result;
}
template<class T>
static T getValue(const std::string& option)
{
T result;
if(!isParsed())
throw std::exception("You must call parseOptions before trying to retrieve values");
if(_variables_map.count(option))
result = _variables_map[option].as<T>();
return result;
}
private:
OptionsParser();
static boost::program_options::variables_map _variables_map;
static bool _parsed;
static std::vector<std::string> _unrecognized;
static boost::program_options::options_description* _all_options;
}
The real meat of this class are the two methods registerOptions and parseOptions.
registerOptions is used from the constructor of some static class instances I have which setup the options when statics are initialized (before main).
The OptionsProvider class is what I use for this, along with some macros:
class OptionDefinition
{
public:
OptionDefinition(const std::string& option_string, boost::program_options::value_semantic* value, const std::string& description);
const std::string& getOptionString(void) const;
const boost::program_options::value_semantic* getValueSemantic(void) const;
const std::string& getDescription(void) const;
private:
std::string _option_string;
boost::program_options::value_semantic* _value_semantic;
std::string _description;
};
class OptionsProvider
{
public:
OptionsProvider(const std::string& owner, const std::string& description, OptionDefinition* options[]);
virtual ~OptionsProvider(void);
const std::string& getOwner(void) const;
const std::string& getDescription(void) const;
const boost::program_options::options_description& getOptions(void) const;
private:
std::string _owner;
std::string _description;
boost::program_options::options_description _options;
};
#define BEGIN_OPTIONS_ARRAY(owner) static OptionDefinition* Options##owner[] = {
#define DECLARE_OPTION(option_string, value_type, description) new OptionDefinition((option_string), \
new boost::program_options::typed_value<value_type>(NULL), \
(description)),
#define DECLARE_BOOL_OPTION(option_string, description) new OptionDefinition((option_string), \
(new boost::program_options::typed_value<bool>(NULL))->default_value(0)->zero_tokens(), \
(description)),
#define DECLARE_DEFAULT_OPTION(option_string, value_type, def_value, description) new OptionDefinition((option_string), \
(new boost::program_options::typed_value<value_type>(NULL))->default_value(def_value), \
(description)),
#define END_OPTIONS_ARRAY() NULL }; // add terminator to the end of the list
#define DECLARE_OPTIONS_PROVIDER(owner, description) static OptionsProvider OptionsProvider##owner(#owner, description, Options##owner);
#define OPTIONS_PROVIDER(owner) OptionsProvider##owner
Now, here is an example usage of these macros:
BEGIN_OPTIONS_ARRAY(TestManager)
DECLARE_DEFAULT_OPTION("testid", unsigned long, 0L, "Step ID from automation")
DECLARE_DEFAULT_OPTION("timeout", int, -1, "Number of seconds before stopping a\ntest with a timeout result")
DECLARE_DEFAULT_OPTION("board-path", fs::path, fs::path("boards"), "Path to directory containing board libraries")
DECLARE_DEFAULT_OPTION("test-config", fs::path, fs::path(""), "Path to file containing the test configuration")
DECLARE_DEFAULT_OPTION("manager-path", fs::path, fs::path("managers"), "Path to directory container manager libraries")
DECLARE_DEFAULT_OPTION("output-dir", fs::path, fs::path("output"), "Path to output directory")
END_OPTIONS_ARRAY()
DECLARE_OPTIONS_PROVIDER(TestManager, "TestManager options")
This sets up the array and then creates a static OptionsProvider instance which will register the options with the OptionParser static class. Then I can just call OptionsParser::parseOptions(vector_created_from_argv) and it will parse all the options into the boost::program_options::variables_map.
If I have a plug-in class that is loaded later, I just do something like this after it’s options have been registered:
OptionsParser::parseOptions(OptionsParser::getUnrecognizedOptions())
I could probably just have an overload of parseOptions with no parameter and have it automatically use the unrecognized options, but I haven’t decided on that yet.
Then in my classes I can do stuff like this to get option values:
_testid = OptionsParser::getValue<int>("test-id");
to retrieve the value. If I used DECLARE_DEFAULT_OPTION, it automatically sets up the default value since the boost library sets that up. If I don’t, then I’d probably call the getValue overload that takes a default value.