【问题标题】:Read/Write inifiles with boost::{program_options,property_tree}使用 boost::{program_options,property_tree} 读/写 inifile
【发布时间】:2018-10-18 09:29:12
【问题描述】:

使用boost,我想

  1. 从 inifile 中读取选项,如果在 inifile 中遇到未知选项则中止,并且
  2. 稍后将它们保存在另一个 inifile 中。

第一部分可以用 boost::program_options 来完成:

try{
    inifile_options.add_options()
    ("ops1.i0", po::value<int>(&p.nx)->default_value(1), "test integer")
    ;

    po::variables_map vm;
    po::store(po::parse_config_file(pthfnini, inifile_options), vm);
    po::notify(vm);
}   
catch(exception& e){
    cerr << "error: " << e.what() << "\n";
    errorflag=1;
}

据我所知,使用 boost::program_options 无法编写 inifile,但 boost::property_tree 可以:

pt::ptree iniPropTree;
pt::ini_parser::write_ini("./used0.ini",iniPropTree);

现在的问题是如何将存储在 po::variables_map 中的数据转换为 pt::ptree?

阅读 boost 文档给我的印象是这是不可能的。以下是唯一可行的方法吗?

iniPropTree.put<int>("ops1.i0",vm["ops1.i0"].as<int>();

根据我的口味,它引入了相当多的冗余。但是,从一开始就将数据读入属性树似乎不支持检查未定义/拼写错误的选项。

或者,是否可以遍历 variables_map 的内容并以某种方式推断每个元素的相应数据类型?

完整代码在这里:

/*
 * g++ iniOps_test.cpp -Wall -std=c++11 -O3 -lboost_system -lboost_program_options -o iniOps_test.exe
 * 
 */

// C++11 & Boost libraries
#include <boost/program_options.hpp>            // po::options_description, po::variables_map, ...
#include <boost/property_tree/ptree.hpp>        // pt::ptree
#include <boost/property_tree/ini_parser.hpp>   // write_ini()
#include <iostream>                             // cout
#include <fstream>                              // ofstream, ifstream


// namespaces
namespace po = boost::program_options;
namespace pt = boost::property_tree;
using namespace std;


struct params{
    std::string inipthfn;
    int i0;
};


void read_inifile(params &p, po::variables_map &vm){

    // initialize variables
    int errorflag=0;
    std::ifstream pthfnini("./testini.ini");
    po::options_description inifile_options("Allowed inifile options");

    try{
        inifile_options.add_options()
        ("ops1.i0", po::value<int>(&p.i0)->default_value(1), "test integer")
        ;

        ;
        po::store(po::parse_config_file(pthfnini, inifile_options), vm);
        po::notify(vm);
    }
    catch(exception& e){
        cerr << "error: " << e.what() << "\n";
        errorflag=1;
    }

    pthfnini.close();
    if(errorflag){ std::cout<<"--- program shutdown due to error in read_inifile ---"<<std::endl; exit(1); }
}


int main(){

    params p;
    po::variables_map vm;
    pt::ptree iniPropTree;

    read_inifile(p,vm);                                     // get options from inifile

    // ??? conversion from vm -> pt ???

    pt::ini_parser::write_ini("./used0.ini",iniPropTree);   // save options to used.ini
    cout << p.i0 << endl;

    return 0;
}

ini文件“testini.ini”的内容是:

[ops1]
i0=2

【问题讨论】:

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


    【解决方案1】:

    这里有一个概念问题。

    命令行参数本质上是文本的。

    变量映射中的值不是。使用的类型在值语义(选项描述的一部分)中配置。

    如果您的所有选项都具有相同的类型,您可以“作弊”并硬编码转换:

    pt::ptree to_ptree(po::variables_map const& vm) {
        pt::ptree tree;
        for (auto& v : vm) {
            if (!v.second.empty() && !v.second.defaulted())
                tree.put(v.first, v.second.as<int>());
        }
    
        return tree;
    }
    

    节省:

    [ops1]
    i0=1
    

    如果您需要更大的灵活性,您至少需要访问选项说明。这不是该库的预期用途,您可能很快就会遇到实现中未记录的部分。

    【讨论】:

      【解决方案2】:

      但是,从一开始就将数据读入属性树似乎不支持检查未定义/拼写错误的选项

      嗯。这并不完全正确。您可以制作自己的解析函数来添加逻辑。如果需要,请使用属性树转换器。

      这是一个扩展示例,显示了要验证的三个不同类型的参数:

      enum class restricted { value1, value2 };
      
      struct params {
          int        i0  = 1;
          restricted r1  = restricted::value2;
          std::string s2 = "some default";
      };
      

      我们希望有一个这样的解析函数:

      params read_inifile(std::string filename) {
          params p;
          pt::ptree tree;
          std::ifstream file(filename);
      
          read_ini(file, tree);
          p.i0 = tree.get("ops1.i0", 1);
          p.r1 = tree.get("ops1.r1", restricted::value2);
          p.s2 = tree.get("ops1.s2", "some default");
      
          return p;
      }
      

      可流式传输的类型

      要翻译和验证枚举,您只需要实现流式操作符:

      static inline std::istream& operator>>(std::istream& is, restricted& r) {
          std::string v;
          if (is >> std::ws >> v) {
              if (boost::iequals("value1", v))
                  r = restricted::value1;
              else if (boost::iequals("value2", v))
                  r = restricted::value2;
              else
                  throw std::runtime_error("invalid restricted value");
          }
          return is;
      }
      
      static inline std::ostream& operator<<(std::ostream& os, restricted r) {
          switch(r) {
              case restricted::value1: return os << "value1";
              case restricted::value2: return os << "value2";
              default:                 return os << "invalid";
          }
      }
      

      自定义翻译器

      假设i0 需要自定义验证。在这个例子中,我们要求它是一个奇数:

      namespace translators {
      
          template <typename T>
          struct must_be_odd {
              typedef T internal_type;
              typedef T external_type;
      
              boost::optional<T> get_value(const std::string& str) const {
                  if (str.empty()) return boost::none;
      
                  T v = boost::lexical_cast<T>(str);
      
                  if (v % 2 == 0)
                      throw std::runtime_error("value must be odd");
      
                  return boost::make_optional(v);
              }
      
              boost::optional<std::string> put_value(const T& i0) {
                  assert(i0 % 2); // assert that the value was odd
                  return boost::lexical_cast<std::string>(i0);
              }
          };
      
          static const must_be_odd<int> i0;
      } 
      

      现在我们可以简单地提供翻译器(在这里,更像是自定义验证器,例如 Boost Program Options 也有它们):

          p.i0 = tree.get("ops1.i0", 1, translators::i0);
      

      Live On Coliru

      不支持的选项

      这是一个多一点的工作。您必须根据已知集迭代树检查结果路径。这是一个相当通用的实现(它应该与任何(宽)字符串类型的区分大小写的树一起正常工作):

      template <typename Tree, 
               typename Path = typename Tree::path_type,
               typename Key = typename Path::key_type,
               typename Cmp = typename Tree::key_compare>
      std::size_t unsupported(Tree const& tree, std::set<Key, Cmp> const& supported, Path prefix = "") {
          if (tree.size()) {
              std::size_t n = 0;
              for (auto& node : tree) {
                  Path sub = prefix;
                  sub /= node.first;
                  n += unsupported(node.second, supported, sub);
              }
              return n;
          } else {
              if (!supported.count(prefix.dump()) && tree.template get_value_optional<std::string>())
                  return 1;
          }
          return 0;
      }
      

      你可以这样使用它:

      if (auto n = unsupported(tree, {"ops1.i0", "ops1.r1", "ops2.s2"})) {
          throw std::runtime_error(std::to_string(n) + " unsupported options");
      }
      

      完整演示

      Live On Coliru

      #include <boost/algorithm/string.hpp>
      #include <iostream>
      #include <set>
      
      enum class restricted { value1, value2 };
      
      static inline std::istream& operator>>(std::istream& is, restricted& r) {
          std::string v;
          if (is >> std::ws >> v) {
              if (boost::iequals("value1", v))
                  r = restricted::value1;
              else if (boost::iequals("value2", v))
                  r = restricted::value2;
              else
                  throw std::runtime_error("invalid restricted value");
          }
          return is;
      }
      
      static inline std::ostream& operator<<(std::ostream& os, restricted r) {
          switch(r) {
              case restricted::value1: return os << "value1";
              case restricted::value2: return os << "value2";
              default:                 return os << "invalid";
          }
      }
      
      struct params {
          int        i0  = 1;
          restricted r1  = restricted::value2;
          std::string s2 = "some default";
      };
      
      #include <boost/property_tree/ini_parser.hpp>
      #include <boost/lexical_cast.hpp>
      #include <fstream>
      namespace pt = boost::property_tree;
      
      namespace translators {
      
          template <typename T>
          struct must_be_odd {
              typedef T internal_type;
              typedef T external_type;
      
              boost::optional<T> get_value(const std::string& str) const {
                  if (str.empty()) return boost::none;
      
                  T v = boost::lexical_cast<T>(str);
      
                  if (v % 2 == 0)
                      throw std::runtime_error("value must be odd");
      
                  return boost::make_optional(v);
              }
      
              boost::optional<std::string> put_value(const T& i0) {
                  assert(i0 % 2); // assert that the value was odd
                  return boost::lexical_cast<std::string>(i0);
              }
          };
      
          static const must_be_odd<int> i0;
      } 
      
      template <typename Tree, 
               typename Path = typename Tree::path_type,
               typename Key = typename Path::key_type,
               typename Cmp = typename Tree::key_compare>
      std::size_t unsupported(Tree const& tree, std::set<Key, Cmp> const& supported, Path prefix = "") {
          if (tree.size()) {
              std::size_t n = 0;
              for (auto& node : tree) {
                  Path sub = prefix;
                  sub /= node.first;
                  n += unsupported(node.second, supported, sub);
              }
              return n;
          } else {
              if (!supported.count(prefix.dump()) && tree.template get_value_optional<std::string>())
                  return 1;
          }
          return 0;
      }
      
      params read_inifile(std::string filename) {
          params p;
          try {
              pt::ptree tree;
              std::ifstream file(filename);
      
              read_ini(file, tree);
      
              p.i0 = tree.get("ops1.i0", 1, translators::i0);
              p.r1 = tree.get("ops1.r1", restricted::value2);
              p.s2 = tree.get("ops1.s2", "some default");
      
              if (auto n = unsupported(tree, {"ops1.i0", "ops1.r1", "ops2.s2"})) {
                  throw std::runtime_error(std::to_string(n) + " unsupported options");
              }
          } catch (std::exception const& e) {
              std::cerr << "error: " << e.what() << "\n";
              throw std::runtime_error("read_inifile");
          }
      
          return p;
      }
      
      pt::ptree to_ptree(params const& p) {
          pt::ptree tree;
      
          tree.put("ops1.i0", p.i0, translators::i0);
          tree.put("ops1.r1", p.r1);
          tree.put("ops1.s2", p.s2);
      
          return tree;
      }
      
      int main() {
          params const p = read_inifile("./testini.ini"); // get options from filename
          write_ini("./used0.ini", to_ptree(p)); // save options to used.ini
      
          std::cout << p.i0 << std::endl;
      }
      

      对于像这样的输入

      [ops1]
      i0=17
      i99=oops
      [oops1]
      also=oops
      

      打印

      error: 2 unsupported options
      terminate called after throwing an instance of 'std::runtime_error'
        what():  read_inifile
      

      还有changing 17 to 18 prints:

      error: value must be odd
      terminate called after throwing an instance of 'std::runtime_error'
        what():  read_inifile
      

      在有效输入时,used0.ini 将按预期写入:

      [ops1]
      i0=1
      r1=value2
      s2=some default
      

      【讨论】:

        【解决方案3】:

        在给这个问题更多时间之后,我找到了一个合适的紧凑解决方案:

        关键是编写一个函数,根据它们的数据类型将 variables_map 中的条目转换为 propTree(感谢 sehe 让我走上正轨):

        void translate_variables_map_to_ptree(po::variables_map &vm, pt::ptree &propTree){
        
            for(po::variables_map::iterator it=vm.begin(); it!=vm.end(); it++){
                if( it->second.value().type() == typeid(int) ){ propTree.put<int>(it->first,vm[it->first].as<int>()); }
                else if( it->second.value().type() == typeid(float) ){ propTree.put<float>(it->first,vm[it->first].as<float>()); }
                else if( it->second.value().type() == typeid(double) ){ propTree.put<double>(it->first,vm[it->first].as<double>()); }
                else if( it->second.value().type() == typeid(std::string) ){ propTree.put<std::string>(it->first,vm[it->first].as<std::string>()); }
                else if( it->second.value().type() == typeid(size_t) ){ propTree.put<size_t>(it->first,vm[it->first].as<size_t>()); }
                else{ printf("Error: unknown datatype. Abort!\n"); exit(EXIT_FAILURE); }
            }
        }
        

        完整的工作示例写入包含所有读取信息的正确 inifile:

        /*
         * g++ iniOps_test.cpp -Wall -std=c++11 -O3 -lboost_system -lboost_program_options -o iniOps_test.exe
         * 
         */
        
        // C++11 & Boost libraries
        #include <boost/program_options.hpp>            // po::options_description, po::variables_map, ...
        #include <boost/property_tree/ptree.hpp>        // pt::ptree
        #include <boost/property_tree/ini_parser.hpp>   // write_ini()
        #include <iostream>                             // cout
        #include <fstream>                              // ofstream, ifstream
        
        
        // namespaces
        namespace po = boost::program_options;
        namespace pt = boost::property_tree;
        using namespace std;
        
        
        struct params{
            std::string s0;
            int i0;
        };
        
        
        void read_inifile(params &p, po::variables_map &vm){
        
            // initialize variables
            int errorflag=0;
            std::ifstream pthfnini("./testini.ini");
            po::options_description inifile_options("Allowed inifile options");
        
            try{
                inifile_options.add_options()
                ("ops1.i0", po::value<int>(&p.i0)->default_value(1), "test integer")
                ("ops1.s0", po::value<std::string>(&p.s0)->default_value("default"), "test string")
                ;
        
                ;
                po::store(po::parse_config_file(pthfnini, inifile_options), vm);
                po::notify(vm);
            }
            catch(exception& e){
                cerr << "error: " << e.what() << "\n";
                errorflag=1;
            }
        
            pthfnini.close();
            if(errorflag){ std::cout<<"--- program shutdown due to error in read_inifile ---"<<std::endl; exit(1); }
        }
        
        void translate_variables_map_to_ptree(po::variables_map &vm, pt::ptree &propTree){
        
            for(po::variables_map::iterator it=vm.begin(); it!=vm.end(); it++){
                if( it->second.value().type() == typeid(int) ){ propTree.put<int>(it->first,vm[it->first].as<int>()); }
                else if( it->second.value().type() == typeid(float) ){ propTree.put<float>(it->first,vm[it->first].as<float>()); }
                else if( it->second.value().type() == typeid(double) ){ propTree.put<double>(it->first,vm[it->first].as<double>()); }
                else if( it->second.value().type() == typeid(std::string) ){ propTree.put<std::string>(it->first,vm[it->first].as<std::string>()); }
                else if( it->second.value().type() == typeid(size_t) ){ propTree.put<size_t>(it->first,vm[it->first].as<size_t>()); }
                else{ printf("Error: unknown datatype. Abort!\n"); exit(EXIT_FAILURE); }
            }
        }
        
        
        int main(){
        
            params p;
            po::variables_map vm;
            pt::ptree iniPropTree;
        
            read_inifile(p,vm);                                     // get options from inifile
            translate_variables_map_to_ptree(vm,iniPropTree);       // conversion from vm -> pt 
            pt::ini_parser::write_ini("./used0.ini",iniPropTree);   // save options to used.ini
        
            cout << p.i0 << endl;
            cout << p.s0 << endl;
        
            return 0;
        }
        

        通过读取命令行获取 variables_map vm,也可以通过以下方式更新属性树中的值(通过读取 inifile):

        string opsName = "ops1.i0"; if(vm.count(opsName)) p.i0 = vm[opsName].as<int>();
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-03-21
          • 1970-01-01
          • 2017-12-27
          • 1970-01-01
          • 2015-02-05
          • 2019-01-14
          • 1970-01-01
          • 2013-09-24
          相关资源
          最近更新 更多