在C ++中存储常量数据的最佳方法

时间:2009-09-28 09:23:27

标签: c++

我有一系列常量数据,如下所示:

enum Language {GERMAN=LANG_DE, ENGLISH=LANG_EN, ...};
struct LanguageName {
    ELanguage language;
    const char *name;
};

const Language[] languages = {
    GERMAN, "German",
    ENGLISH, "English",
    .
    .
    .
};

当我有一个访问数组并根据Language enum参数找到条目的函数时。我应该编写一个循环来查找数组中的特定条目,还是有更好的方法来执行此操作。

我知道我可以将LanguageName-objects添加到std :: map中,但对于这样一个简单的问题,这不会有点过分吗?我没有一个存储std :: map的对象,因此每次调用函数时都会构造映射。

你会推荐什么方式?

将此编译时常量数组封装在处理查找的类中是否更好?

7 个答案:

答案 0 :(得分:3)

如果enum值从0开始是连续的,请使用枚举作为索引的数组。

如果没有,这就是我通常做的事情:

const char* find_language(Language lang)
{
  typedef std::map<Language,const char*> lang_map_type;
  typedef lang_map_type::value_type lang_map_entry_type;

  static const lang_map_entry_type lang_map_entries[] = { /*...*/  }
  static const lang_map_type lang_map( lang_map_entries
                                     , lang_map_entries + sizeof(lang_map_entries)
                                                        / sizeof(lang_map_entries[0]) );
  lang_map_type::const_iterator it = lang_map.find(lang);
  if( it == lang_map.end() ) return NULL;
  return it->second;
}

如果考虑常量贴图,请始终考虑使用矢量。

函数局部静态是摆脱全局变量的大部分依赖性问题的好方法,但在多线程环境中是危险的。如果您对此感到担心,您可能更愿意使用全局变量:

typedef std::map<Language,const char*> lang_map_type;
typedef lang_map_type::value_type lang_map_entry_type;

const lang_map_entry_type lang_map_entries[] = { /*...*/  }
const lang_map_type lang_map( lang_map_entries
                            , lang_map_entries + sizeof(lang_map_entries)
                                               / sizeof(lang_map_entries[0]) );

const char* find_language(Language lang)
{
  lang_map_type::const_iterator it = lang_map.find(lang);
  if( it == lang_map.end() ) return NULL;
  return it->second;
}

答案 1 :(得分:2)

我可以选择三种基本方法。一个是switch语句,在某些条件下它是一个非常好的选择。请记住 - 编译器可能会将其编译为有效的表查找,但它会查找指向代码块而不是数据值的指针。

选项二和三涉及您正在使用的类型的静态数组。选项二是一个简单的线性搜索 - 您(我认为)已经在做 - 如果项目数量很少,则非常合适。

选项三是二分搜索。静态数组可以与标准库算法一起使用 - 只需使用第一个和第一个+计数指针,就像使用开始和结束迭代器一样。您需要确保对数据进行排序(使用std :: sort或std :: stable_sort),并使用std :: lower_bound进行二进制搜索。

在这种情况下的复杂性是你需要一个比较函数对象,它像operator&lt;具有存储或引用的值,但只查看结构的键字段。以下是一个粗略的模板...

class cMyComparison
{
  private:
    const fieldtype& m_Value;  //  Note - only storing a reference

  public:
    cMyComparison (const fieldtype& p_Value) : m_Value (p_Value) {}

    bool operator() (const structtype& p_Struct) const
    {
      return (p_Struct.field < m_Value);
        //  Warning : I have a habit of getting this comparison backwards,
        //            and I haven't double-checked this
    }
};

在下一个C ++标准版本中,这种事情应该变得更简单,当IIRC我们将获得匿名函数(lambdas)和闭包。

如果您无法在应用程序初始化中进行排序,则可能需要一个已排序的布尔静态变量,以确保只排序一次。

注意 - 这仅供参考 - 在您的情况下,我认为您应该坚持使用线性搜索或使用switch语句。二元搜索可能只是一个好主意......

  1. 有很多要搜索的数据项
  2. 搜索经常非常(每秒多次)
  3. 关键的枚举值很稀疏(很多大的间隙) - 否则,切换更好。
  4. 如果编码工作是微不足道的,那就没什么大不了的了,但是C ++目前使它变得比它应该更难。

    一个小注意事项 - 为数组的大小定义枚举并确保静态数组声明使用该枚举可能是个好主意。这样,如果您修改表(添加/删除项目)并忘记更新大小枚举,您的编译器应该会抱怨,因此您的搜索不应该丢失项目或超出范围。

答案 2 :(得分:2)

我认为你有两个问题:

  • 存储常量全局变量的最佳方法是什么(可能有多线程访问)?
  • 如何存储您的数据(使用哪个容器)?

sbi描述的解决方案很优雅,但你应该知道2个潜在的问题:

  1. 如果是多线程访问,可以初步进行初始化。
  2. 您可能会在销毁该变量后尝试访问该变量。
  3. another thread中涵盖了静态对象生命周期的两个问题。

    让我们从常量全局变量存储问题开始。

    因此,如果您不关心1.或2,则sbi提出的解决方案就足够了。在任何其他情况下,我建议使用Singleton,例如Loki提供的解决方案。阅读相关文档以了解有关生命周期的各种政策,这是非常有价值的。

    我认为使用数组+地图似乎很浪费,看到它会伤到我的眼睛。我个人更喜欢稍微优雅(imho)的解决方案。

    const char* find_language(Language lang)
    {
      typedef std::map<Language, const char*> map_type;
      typedef lang_map_type::value_type value_type;
    
      // I'll let you work out how 'my_stl_builder' works,
      // it makes for an interesting exercise and it's easy enough
      // Note that even if this is slightly slower (?), it is only executed ONCE!
      static const map_type = my_stl_builder<map_type>()
                              << value_type(GERMAN, "German")
                              << value_type(ENGLISH, "English")
                              << value_type(DUTCH, "Dutch")
                              ....
                              ;
    
      map_type::const_iterator it = lang_map.find(lang);
      if( it == lang_map.end() ) return NULL;
      return it->second;
    }
    

    现在进入容器类型问题。

    如果您关注性能,那么您应该意识到,对于小数据收集,对向量通常在查找中比在地图上更有效。我再次转向Loki(及其AssocVector),但我认为你不应该担心性能。

    我倾向于选择我的容器,这取决于我可能首先需要的界面,这里的地图界面真的是你想要的。

    另外:为什么你使用'const char *'而不是'std :: string'?

    我见过太多人使用'const char *'就像std :: string(就像忘记你必须使用strcmp一样)被所谓的内存/性能损失所困扰......

答案 3 :(得分:0)

这取决于数组的用途。如果您计划在列表中显示值(可能是用户选择),则阵列将是存储它们的最有效方式。如果您计划通过enum键频繁查找值,则应该研究一种更有效的数据结构,例如地图。

答案 4 :(得分:0)

无需编写循环。您可以使用枚举值作为数组的索引。

答案 5 :(得分:0)

我会使用顺序语言代码进行枚举

enum { GERMAN=0, ENGLISH, SWAHILI, ENOUGH };

将它们全部放入数组

const char *langnames[] = {
    "German", "English", "Swahili"
};

然后我会检查调试版本中是否sizeof(langnames)==sizeof(*langnames)*ENOUGH

并祈祷我没有重复或交换语言; - )

答案 6 :(得分:0)

如果您想要快速简单的解决方案,可以尝试这样

enum ELanguage {GERMAN=0, ENGLISH=1};

    static const string Ger="GERMAN";
    static const string Eng="ENGLISH";

    bool getLanguage(const ELanguage& aIndex,string & arName)
    {
        switch(aIndex)
        {
        case GERMAN:
            {
                arName=Ger;
                return true;
            }
        case ENGLISH:
            {
                arName=Eng;
            }
        default:
            {
                // Log Error
                return false;
            }
        }
    }