将字符串字段映射到变量的更好方法

时间:2018-06-15 06:42:20

标签: c++ windows

我有一些全局变量,一旦读取配置文件就会赋值。

bool bar1;
int bar2;
string bar3;

我读了下面的配置文件:

foo1 = 12
foo2 = 0
foo3 = 1
...
void func()
{
   //read file into a std::map mp
   for(auto i:mp)
   {
      if(i.first=="foo1")
         bar1 = i.second;
      else if(i.first=="foo2")
         bar2 = i.second;
      else if(i.first=="foo3")
         bar3 = i.second;
       .....
   }
} 

我有很多这样的变量要从文件初始化。有没有更好的方法来做到这一点,因为这会使我的功能膨胀。

PS:我仍然坚持使用C ++ 03。

1 个答案:

答案 0 :(得分:0)

在我的评论中,我详细阐述了Jabberwocky使用std::map的想法。

实际上,我们在S / W中做了类似的配置和类似的事情。唯一的区别 - 我们不会使用std::map作为预定义数组。 (我不喜欢在运行时必须完成某些事情的想法,这在编译之后实际上永远不会改变。)为了演示这个概念,我做了一点MCVE

#include <iostream>
#include <cassert>
#include <cstring>
#include <algorithm>
#include <map>

int main()
{
  // variables
  int bar1 = 0, bar2 = 0, bar3 = 0;
  // symbol table
  const struct Entry { const char *key; int *pVar; } table[] = {
    { "foo1", &bar1 },
    { "foo2", &bar2 },
    { "foo3", &bar3 }
  };
  const size_t nTable = sizeof table / sizeof *table;
  // check that table has correct order
  assert([&]()
    {
      for (size_t i = 1; i < nTable; ++i) {
        if (strcmp(table[i - 1].key, table[i].key) >= 0) return false;
      }
      return true;
    }());
  // use table in tests
  std::pair<const char*, int> mp[] = {
    { "foo1", 123 },
    { "foo2", 234 },
    { "foo3", 345 },
    { "wrong", 666 }
  };
  // evaluate mp of OP
  for (auto i : mp) {
    const Entry e = { i.first, 0 };
    const auto iter
      = std::lower_bound(std::begin(table), std::end(table), e,
        [](const Entry &e1, const Entry &e2) { return strcmp(e1.key, e2.key) < 0; });
    if (iter != std::end(table) && strcmp(iter->key, i.first) == 0) *iter->pVar = i.second;
    else std::cerr << "Unknown var '" << i.first << "'!\n";
  }
  // print result
  std::cout
    << "bar1: " << bar1 << '\n'
    << "bar2: " << bar2 << '\n'
    << "bar3: " << bar3 << '\n';
  // done
  return 0;
}

输出:

Unknown var 'wrong'!
bar1: 123
bar2: 234
bar3: 345

Live Demo on coliru

基本部分是struct Entry,它将选项的名称与相应变量的地址分组。这可用于在std::map中存储名称和变量地址对。

我使用的是预先排序的数组。 (在编程中手动对键进行排序并不困难 - 如果发生意外,assert()会发出警报。)

在我们高效的S / W中,我们没有使用变量的地址,但是使用方法指针来设置setter函数,因为目标变量具有不同的类型,并且值(作为字符串提供)是resp的主题。解析。但是,这些方法指针是编译时可解决的→整个表可以是static。因此,防止了为每个函数调用构建表的努力。在此演示中,表将地址存储到局部变量。这让我觉得static表可能是一个坏主意(我甚至没有尝试过)。

根据请求,这里使用方法指针设置setter方法的另一个演示:

#include <iostream>
#include <cassert>
#include <cstring>
#include <string>
#include <algorithm>

class Object {
  private:
    // some member variables:
    int var1, var2;
    std::string var3;
    double var4;

  public:
    Object(): var1(), var2(), var4() { }

    friend std::ostream& operator<<(std::ostream &out, const Object &obj);

    // the setter methods
    void setVar1(const char *value) { var1 = atoi(value); }
    void setVar2(const char *value) { var2 = atoi(value); }
    void setVar3(const char *value) { var3 = value; }
    void setVar4(const char *value) { var4 = strtod(value, nullptr); }

    // the config method to set value by text    
    void config(const char *key, const char *value)
    {
      // symbol table
      static const struct Entry {
        const char *key; // the symbol
        void (Object::*set)(const char*); // the corresponding setter method
      } table[] = {
        { "var1", &Object::setVar1 },
        { "var2", &Object::setVar2 },
        { "var3", &Object::setVar3 },
        { "var4", &Object::setVar4 }
      };
      enum { nTable = sizeof table / sizeof *table };
      // check that table has correct order (paranoid - debug only code)
      assert([&]()
        {
          for (size_t i = 1; i < nTable; ++i) {
            if (strcmp(table[i - 1].key, table[i].key) >= 0) return false;
          }
          return true;
        }());
      // find setter by key
      const Entry e = { key, nullptr };
      const auto iter
        = std::lower_bound(std::begin(table), std::end(table), e,
          [](const Entry &e1, const Entry &e2) { return strcmp(e1.key, e2.key) < 0; });
      if (iter != std::end(table) && strcmp(iter->key, key) == 0) {
        (this->*iter->set)(value);
      } else std::cerr << "Unknown var '" << key << "'!\n";
    }
};

std::ostream& operator<<(std::ostream &out, const Object &obj)
{
  return out
    << "var1: " << obj.var1 << ", var2: " << obj.var2
    << ", var3: '" << obj.var3 << "', var4: " << obj.var4;
}

int main()
{
  Object obj;
  // print obj before config:
  std::cout << "obj: " << obj << '\n';
  // configure obj
  std::pair<const char*, const char*> config[] = {
    { "var1", "123" },
    { "var2", "456" },
    { "var3", "text" },
    { "var4", "1.23" },
    { "evil", "666" }
  };
  for (const auto& entry : config) {
    obj.config(entry.first, entry.second);
  }
  // print obj after config:
  std::cout << "obj: " << obj << '\n';
  // done
  return 0;
}

输出:

obj: var1: 0, var2: 0, var3: '', var4: 0
Unknown var 'evil'!
obj: var1: 123, var2: 456, var3: 'text', var4: 1.23

tableObject::config())中的内容为static const,将在编译时构建(希望&#34;烧掉&#34;到二进制文件中)。因此,Object::config()的多次调用只能对匹配的key进行二进制搜索,并在成功的情况下调用setter。

一个重要的前提条件是所有setter方法都具有相同的签名。否则,将它们存储在数组中将是不可能的,因为它们都必须与数组中的方法指针元素兼容。

Live Demo on coliru