【问题标题】:Change value semantics in boost::program_options after add_options(). I.e. default value在 add_options() 之后更改 boost::program_options 中的值语义。 IE。默认值
【发布时间】:2013-01-09 20:24:45
【问题描述】:

我偶然发现了标题中定义的问题。我有一个创建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(),添加更多特定选项。这看起来很整洁,而且我很快就可以使用它。

然后我注意到所有派生类都有一个通用选项,但如果它有一个不同的默认值会非常好。 IE。输出文件的名称。让 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 设置得非常高,那就太好了。

【问题讨论】:

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


    【解决方案1】:

    您可以调用options_description::find("optimization", ...) 来获取对关联option_description 的引用,其semantic 方法将为您提供指向您最初在调用add_options 时提供的value_semantic 的指针。但是,它是一个 const 指针,因此您似乎不允许修改它所指向的内容。

    但是,value_semantic 在创建时不是 const,这意味着使用 const_cast 删除 option_description 应用的 const 限定符应该是安全的。您还必须将 value_semantic 对象类型转换回您最初调用 value&lt;T&gt; 时获得的正确 typed_value 类型。

    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)
    { }
    

    【讨论】:

    • 好的,所以这真的是不可能的。后来我发现了如何关联value_semantic,但是,正如你所说,它是一个常量引用。所以只剩下解决方法了。我还有另一个想法。创建一个私有虚拟方法add_optimization_option() 并在派生类中重载它。我不喜欢在类层次结构中向上传递参数的想法。不知道为什么。我也认为它可能比你的建议更好,因为它不会使构造函数混乱。如果我的用例很奇怪,您能否发表评论?也许我应该向开发人员询问功能或理由。
    • 回想一下,您问的主要问题是事后如何更改value_semantic。 “不违反 program_options 的设计就不可能”与“不太可能”完全不同。这里的代码不这样做吗(或类似的东西,因为我没有测试它)?
    • 对我来说好吧,使用const_cast 使之成为可能。然而,由于库的设计者引用了const,他们阻止了故意修改对象的能力。你不能说,可以安全地修改value_semanticoption_description。你可以这样做,但它不安全。因此 italic '真的'。即使你说它应该是安全的。它不必。我们还要做的是提出变通办法来获得预期的行为并使用正确设置的选项实例化m_desc。无论如何,谢谢,您的cmets对我帮助很大。 +1
    【解决方案2】:

    const std::vector&lt; shared_ptr&lt; option_description &gt; &gt; &amp; options_description::options() const; 为您提供对option_description 的写入权限。

    const shared_ptr&lt;T&gt; 不是 shared_ptr&lt;const T&gt;

    但是,如果我遇到这种问题,我会改用add_output_file( std::string default ),并让它调用add_option。 (很可能,尽管从界面上看,上述内容似乎是合法的,但像这样弄乱内部结构可能会使boost 库感到困惑。

    【讨论】:

      【解决方案3】:

      好的,所以我发现在当前版本中,在不违反 const_casting 的现有设计的情况下更改 value_semantic 是不可能的。

      在阅读了 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");
      }
      

      成本是为每个修改的选项添加一个私有方法,并为我们想要修改它的每个案例重载一个私有方法。当一个人想要让它保持默认时,什么都不需要。 如果可以修改value_semantic,则可以避免定义新方法。不过,除了这个障碍之外,它运行良好,不会弄乱其他任何东西。

      【讨论】:

      • 记得给add_optimization_value打电话是谁的责任?基类在构造函数中设置了所有其他选项,因此最好简单地调用构造函数并完成它,但BaseClass::BaseClass() 不能调用重写的虚拟方法。任何构建此类实例的人都需要记住还有一个额外的初始化步骤。我更喜欢我的构造函数参数的想法,因为这样就不可能不从对象生命周期的一开始就设置所有选项。
      • 是的,这就是缺陷。我不知道构造函数不能调用重写的虚拟方法。我实现了它并遇到了这个问题。我通过创建另一个方法init_options 绕过了这个,然后调用了被覆盖的方法。所以派生类不必做任何其他事情。但是必须创建对象,因此它的工作是以额外的初始化步骤为代价的。正如你所说。老实说,我不喜欢我的想法。它被杀了。那是我缺乏知识。我想要你的想法,但在我的情况下它会使构造函数快速混乱。
      猜你喜欢
      • 1970-01-01
      • 2019-01-15
      • 1970-01-01
      • 1970-01-01
      • 2019-07-05
      • 2021-08-08
      • 1970-01-01
      • 1970-01-01
      • 2010-09-27
      相关资源
      最近更新 更多