VVenc 命令行参数解析方式

关于C++命令行参数解析的基础分析可以参考文章C++ 中使用 argc 和 argv 的命令行参数

VVenc中的命令行参数解析的过程相对于上面这篇文章有种羊了个羊第一关到第二关的感觉,这篇文章我们逐一的去分析VVenc命令行参数的解析方式。

VVenc命令行参数相关的结构体和类

首先需要介绍VVenc中几个相关的结构体和类。C++中的struct与class的区别:

  • Class中默认的成员访问权限是private的,而Struct中则是public的;
  • 从Class继承默认是private继承,而从Struct继承默认是public继承。
  • 除此之外C++的结构体和类没有太大的区别。

struct OptionBase

/** OptionBase: Virtual base class for storing information relating to a
 * specific option This base class describes common elements.  Type specific
 * information should be stored in a derived class. */
struct OptionBase
{
  OptionBase(const std::string& name, const std::string& desc, const bool bool_switch )
  : opt_string(name), opt_desc(desc), is_bool_switch(bool_switch)
  {};

  virtual ~OptionBase() {}

  /* parse argument arg, to obtain a value for the option */
  virtual void parse(const std::string& arg, ErrorReporter&) = 0;
  /* set the argument to the default value */
  virtual void setDefault() = 0;
  virtual const std::string getDefault( ) { return std::string(); }
  virtual const std::string getValue( ) { return std::string(); }

  std::string opt_string;
  std::string opt_desc;
  bool        is_bool_switch = false;
};

OptionBase:用于存储与特定参数相关的信息的虚拟基类。这个基类描述了常见元素opt_string和opt_desc。类型特定信息应存储在派生类中。

struct Options


struct Names{
  Names() : opt(0) {};
  ~Names()
  {
    if (opt)
    {
      delete opt;
    }
  }
  std::list<std::string> opt_long;
  std::list<std::string> opt_short;
  OptionBase* opt;
};

typedef std::list<Names*> NamesPtrList;
NamesPtrList opt_list;

typedef std::map<std::string, NamesPtrList> NamesMap;
NamesMap opt_long_map;
NamesMap opt_short_map;

typedef std::list<std::string> subSectionsPtrList;
subSectionsPtrList subSections_list;
std::string curSubSection;
  
typedef std::map<std::string, std::list<std::string> > SubSectionNamesListMap;
SubSectionNamesListMap sub_section_namelist_map;

struct Options定义了以上成员变量,默认都是public类型。

  • Names是一个结构体,结构体内包含了opt_longopt_short两个string型的list;
  • opt_list是Names*类型的list;
  • opt_long_mapopt_short_map是hash map,方便对长短命令行参数的查找;

void addOption(OptionBase *opt)

void addOption(OptionBase *opt)
  {
    Names* names = new Names();
    names->opt = opt;
    std::string& opt_string = opt->opt_string;

    if( useLowerNamesOnly ) // 如果只使用小写字母,则将所有大写字母转换成小写字母
    {
      std::transform( opt_string.begin(), opt_string.end(), opt_string.begin(), ::tolower );
    }

    size_t opt_start = 0;
    for (size_t opt_end = 0; opt_end != std::string::npos;)
    {
      opt_end = opt_string.find_first_of(',', opt_start); // 先找到当前开始位置后第一个逗号的位置
      bool force_short = 0;
      if (opt_string[opt_start] == '-') // 如果以'-'开始,强制为短命令
      {
        opt_start++;
        force_short = 1;
      }
      std::string opt_name = opt_string.substr(opt_start, opt_end - opt_start); // 获取命令
      if (force_short || opt_name.size() == 1) // 如果强制为短命令 或者命令长度是1
      {
        names->opt_short.push_back(opt_name); // push到opt_short list里
        opt_short_map[opt_name].push_back(names); // push到hash表里
      }
      else // 如果不是短命令push到长命令里
      {
        names->opt_long.push_back(opt_name);

        std::string optLongLower = opt_name;
        std::transform( optLongLower.begin(), optLongLower.end(), optLongLower.begin(), ::tolower );
        opt_long_map[optLongLower].push_back(names);
      }
      opt_start += opt_end + 1; // 跳到下一个逗号的位置
    }

    if( !subSections_list.empty() )
    {
      if( curSubSection.empty() ){ curSubSection = subSections_list.back(); }
      sub_section_namelist_map[curSubSection].push_back(opt_string);
    }

    opt_list.push_back(names); // 将长命令和对应的短命令push到opt_list里
  }

函数addOption用存储命令行参数,包括短命令和长命令,其派生类OptionSpecific中对函数调用符进行了重载,并在重载函数中调用了addOption。关于函数调用符的重载可以参考重载函数调用运算符。函数的细节已经在代码里注释了,这里就不再说了。

class OptionSpecific

/* Class with templated overloaded operator(), for use by Options::addOptions() */
class OptionSpecific
{
public:
  OptionSpecific(Options& parent_) : parent(parent_) {}

  /**
   * Add option described by name to the parent Options list,
   *   with storage for the option's value
   *   with default_val as the default value
   *   with desc as an optional help description
   */
  template<typename T>
  OptionSpecific&
  operator()(const std::string& name, T& storage, T default_val, const std::string& desc = "", bool bool_switch = false)
  {
    parent.addOption(new Option<T>(name, storage, default_val, desc, bool_switch));
    return *this;
  }

  /**
   * Add option described by name to the parent Options list,
   *   without separate default value
   */
  template<typename T>
  OptionSpecific&
  operator()(const std::string& name, T& storage, const std::string& desc = "", bool bool_switch = false )
  {
    parent.addOption(new Option<T>(name, storage, storage, desc, bool_switch));
    return *this;
  }

  /**
   * Add option described by name to the parent Options list,
   *   with desc as an optional help description
   * instead of storing the value somewhere, a function of type
   * OptionFunc::Func is called.  It is upto this function to correctly
   * handle evaluating the option's value.
   */
  OptionSpecific&
  operator()(const std::string& name, OptionFunc::Func *func, const std::string& desc = "")
  {
    parent.addOption(new OptionFunc(name, parent, func, desc));
    return *this;
  }
private:
  Options& parent;
};

OptionSpecific对函数调用运算符进行了三次重载,分别是:

  • 将按名称描述的命令行参数添加到父选项列表中,并存命令行参数的值,默认值为default_val,可选帮助描述为desc
  • 将按名称描述的命令行参数添加到父选项列表中,不使用单独的默认值
  • 将按名称描述的命令行参数添加到父选项列表中,使用desc作为可选的帮助描述,而不是将值存储在某处,将调用OptionFunc::Func类型的函数。正确处理命令行参数值的评估取决于此函数