在add_options()之后更改boost :: program_options中的值语义。即默认值

时间:2013-01-09 20:24:45

标签: c++ boost boost-program-options

我偶然发现了标题中定义的问题。我有一个应用程序创建options_description的实例,然后在其上使用add_options()。 与示例非常相似:

options_description desc;
desc.add_options()
("help", "produce help")
("optimization", value<int>()->default_value(10), "optimization level")
;

我的问题是,如何在此次调用后修改optimization的默认值。这甚至可能吗?文档对我来说似乎很模糊。根据我的理解,这个问题可以推广到任何语义值,因为value_semantic是括号中的第二个参数。

动机

我觉得这可能是不可能的。所以我想表达我对这种功能的动力。也许我的意图设计有缺陷,所以你可以提出别的建议。

我有几个程序执行非常相似的任务,并分享相当多的参数和开关。我以为我可以将公共参数重构为单独的基类。我虽然可以以类似的方式重构命令行解析。 boost::program_options我的想法做得很好。我将options_description实例构造为基类中的私有属性,并在那里添加公共选项。然后在初始化的派生类中,我再次在此对象上执行add_options()添加更多特定选项。这看起来很整洁,我的工作速度很快。

然后我注意到所有派生类都有一个共同的选项,但是它具有不同的默认值真的很不错。即输出文件的名称。对于app1是app1.out,app2 - app2.out等。

当然我可以在派生类中将输出文件名选项移动到add_options,但它看起来很愚蠢和冗余,因为即使在语义上,除默认值外,一切都是相同的。另一种解决方法是从基类中的默认值和派生类的后解析步骤中退出,检查选项是否已设置并手动应用(默认)值。但是,这似乎也是多余的,因为预期的功能似乎是在库本身中实现的。

我将尝试提供一个代码示例,以便您以后或根据请求更好地感受它。虽然,我认为我的方法很清楚。

编辑 - 代码示例 它是在Rob的回答之后写的,所以我试图保持在命名惯例中。

Base - 执行解析并允许将优化级别设置为整数:

#include <boost/program_options.hpp>
namespace po = boost::program_options;

class BaseClass {
public:
  BaseClass::BaseClass();
  virtual int parse(const int argc, char** argv);
private:
  po::options_description m_desc;
  po::variables_map vm;
  int optimization_level;
};

BaseClass::BaseClass():
  m_desc()
{
  m_desc.add_options()
   ("help", "produce help")
   ("optimization", value<int>()->default_value(10), "optimization level")
  ;
}

int BaseClass::parse(const int argc, char** argv)
{
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);
  if (vm.count("help")) { std::cout << desc << "\n"; return 1; }
  optimization_level = vm["optimization"].as<int>();
  return 0;
}

高度优化的版本,允许选择执行花哨的东西:

class HighlyOptimizedClass : public BaseClass {
public:
  HighlyOptimizedClass();
  virtual int parse(const int argc, char** argv);
private:
  bool fancy_optimizations;
};

HighlyOptimizedClass(): BaseClass() {
  m_desc.add_options()
   ("fancy,f", po::value<bool>()->zero_tokens(), "perform fancy optimizations")
  ;
}

HighlyOptimizedClass::parse(const int argc, char** argv)
{
  int ret = BaseClass::parse(argc, argv);      //execute base function
  if( ret ) return ret;                        //return if it didnt succed
  if ( vm.count("fancy") ) fancy_optimizations = 1;  // non-base stuff
  return 0;
}

允许打开详细调试的非优化版本:

class NonOptimizedClass : public BaseClass {
public:
  NonOptimizedClass();
  virtual int parse(const int argc, char** argv);
private:
  bool verbose_debug;
};

NonOptimizedClass(): BaseClass() {
  m_desc.add_options()
   ("verbose,v", po::value<bool>()->zero_tokens(), "genrates TONS of output")
  ;
}

NonOptimizedClass::parse(const int argc, char** argv)
{
  int ret = BaseClass::parse(argc, argv);       // execute base function
  if( ret ) return ret;                         // return if it didnt succed
  if ( vm.count("verbose") ) verbose_debug = 1; // non-base stuff
  return 0;
}

我试图垂直压缩它但反正它很长= /。对不起,如果我太过分了。反过来,这些例子是清晰且独立的。

BaseClass设置几乎所有东西并解析常见的东西。派生类在构造函数和重载解析中添加自己的选项。他们会执行基本解析器并检查错误。这也使--help有效。

现在要修改每个派生的优化默认值。因为NonOptimizedClass设置得非常低而OptimizedClass设置得非常高,这样会很好。

3 个答案:

答案 0 :(得分:5)

您可以致电options_description::find("optimization", ...)以获取对相关option_description的引用,其semantic方法会为您提供指向您最初提供的value_semantic的指针add_options。但是,它是一个const指针,因此看起来你不允许修改它所指向的内容。

但是,value_semantic在创建时不是const,这意味着使用const_cast删除option_description适用的const限定条件应该是安全的。您还必须将value_semantic对象压缩回原来调用typed_value时获得的value<T>类型。

option_description const& optimization = desc.find("optimization", false);
shared_ptr<const value_semantic> cvalue = optimization.semantic();
shared_ptr<value_semantic> value = const_pointer_cast<value_semantic>(cvalue);
shared_ptr<typed_value<int>> tvalue = dynamic_pointer_cast<typed_value<int>>(value);
assert(tvalue);
tvalue->default_value(20);

另一种设计,避免在定义后修改选项(显然不是program_options设计要做的事情),是让程序特定的派生类传递所需的默认值基类的值。然后基类可以在定义优化选项时使用该值。

BaseClass::BaseClass(int default_optimization):
  m_desc()
{
  m_desc.add_options()
    ("help",
      "produce help")
    ("optimization",
      value<int>()->default_value(default_optimization),
      "optimization level")
    ;
}

HighlyOptimizedClass::HighlyOptimizedClass():
  BaseClass(99)
{ }

NonOptimizedClass::NonOptimizedClass():
  BaseClass(0)
{ }

答案 1 :(得分:0)

const std::vector< shared_ptr< option_description > > & options_description::options() const;为您提供option_description的写入权限。

const shared_ptr<T>不是shared_ptr<const T>

但是,如果我遇到这种问题,我会改为使用add_output_file( std::string default )并让它调用add_option。 (很可能,尽管上面的界面似乎是合法的,弄乱这样的内部可能会混淆boost库。

答案 2 :(得分:0)

好的,所以我发现在当前版本中,不能改变value_semantic而不会违反program_options const_cast的现有设计。

在阅读了Rob Kennedy和Yakk的回答和建议之后,我想出了一种两者结合的方法。它应该按照描述工作,不应该不必要地混乱。

想法是添加要在单独调用中更改的选项。使其成为虚拟并在基类中定义默认大小写。

此方法允许同时自定义整个program_option,而不仅仅是单个语义。为每个可能改变的单个案例添加参数或方法对我来说似乎非常麻烦。

更改后的代码如下所示:

class BaseClass {
public:
  BaseClass::BaseClass();
  virtual int parse(const int argc, char** argv);
private:
  virtual void add_optimization_option();
  po::options_description m_desc;
  po::variables_map vm;
  int optimization_level;
};

BaseClass::BaseClass(): m_desc() {
  m_desc.add_options()("help", "produce help");
}

void BaseClass::add_optimization_option(){
 m_desc.add_options()
  ("optimization", value<int>()->default_value(10), "optimization level");
}

优化版本:

class HighlyOptimizedClass : public BaseClass {
public:
  HighlyOptimizedClass();
  virtual int parse(const int argc, char** argv);
private:
  virtual void add_optimization_option();
  bool fancy_optimizations;
};

void HighlyOptimizedClass::add_optimization_option(){
  m_desc.add_options()
   ("optimization", value<int>()->default_value(99), "optimization level");
}

非优化的:

class NonOptimizedClass : public BaseClass {
public:
  NonOptimizedClass();
  virtual int parse(const int argc, char** argv);
private:
  virtual void add_optimization_option();
  bool verbose_debug;
};

void NonOptimizedClass::add_optimization_option(){
  m_desc.add_options()
   ("optimization", value<int>()->default_value(0), "optimization level");
}

成本是为每个修改的选项添加一个私有方法,并为我们想要修改它的每个case重载一个私有方法。当一个人想要离开时,默认不需要任何东西。 如果可以修改value_semantic,则可以避免定义新方法。然而,除了这种障碍之外,它运作良好并且不会混淆其他任何东西。