【问题标题】:C++ Long switch statement or look up with a map?C++ 长 switch 语句还是用地图查找?
【发布时间】:2011-01-28 18:34:32
【问题描述】:

在我的 C++ 应用程序中,我有一些值作为代码来表示其他值。为了翻译代码,我一直在争论是使用 switch 语句还是 stl map。开关看起来像这样:

int code;
int value;
switch(code)
{
case 1:
    value = 10;
    break;
case 2:
    value = 15;
    break;
}

地图将是stl::map<int, int>,翻译将是使用代码作为键值的简单查找。

哪个更好/更高效/更清洁/被接受?为什么?

【问题讨论】:

  • @KennyTM - 非常棒。除了没有实际值...
  • +1 回答一个有趣的问题。
  • 您需要多少种不同的翻译?这将对将要进行的决策产生深远的影响。 IE。如果你只有 case 1 和 case 2,你应该用 if 语句替换它。
  • 将选择逻辑放在自己的函数中,这样就可以有一行案例1:return 10;对于每个选项。不必中断也降低了出错的风险。
  • 如果整数存储在一个文件中,正如评论中提到的那样,我会编写一个实用程序,以 .h/.c 文件的形式从这些文件生成查找表并将其编译到项目中.这解决了维护问题。同样,如果值的范围有合理的上限,则静态数组查找表是最快的解决方案。否则,您必须生成填充地图的代码。

标签: c++ dictionary switch-statement std stdmap


【解决方案1】:

就个人而言,我会使用地图,因为它的使用意味着数据查找 - 使用开关通常表示程序行为的差异。此外,使用地图修改数据映射比使用开关更容易。

如果性能是一个真正的问题,分析是获得可用答案的唯一方法。如果分支错误预测足够频繁,则切换可能不会更快。

考虑这一点的另一种方法是将代码和相关值组合到数据结构中是否更有意义,尤其是在代码和值的范围是静态的情况下:

struct Code { int code; int value; };

Code c = ...

std::cout << "Code " << c.code << ", value " << c.value << std::end;

【讨论】:

  • 我喜欢你关于地图暗示数据查找和表示行为差异的开关的观点。
【解决方案2】:

如果您的代码足够连续并且它们的范围允许,那么您最好使用老式的简单数组,例如

int lookup[] = {-1, 10, 15, -1 222};

那么 switch 语句可以简单地重写为

值 = 查找[代码];

所有其他选项都会在一定程度上带来额外的成本。

【讨论】:

  • 它们不连续。这是问题的一部分。我正在尝试有效地将一个随机整数列表转换为另一个相当随机的整数列表。
  • @Rachel:两个随机列表在编译时都知道吗?如果是且不连续性不大,查表还是不错的方法。
  • 如果代码列表有点长,这可能很难维护。假设有一次您被告知代码 145 的值已更改为 8,您现在需要更新源代码。
  • @James - 但您始终可以有一个预编译步骤,从更友好的格式(可能是 JSON 对象?)生成查找表源文件
【解决方案3】:

这取决于代码是什么以及有多少。好的编译器有各种用于优化 switch 语句的技巧,其中一些他们不会使用直接的 if/then 语句。大多数人都足够聪明,可以做简单的数学运算或使用查找/跳转表来处理案例 0、1、2 或案例 3、6、9。

当然有些不会,而且很多很容易被不寻常或不规则的值集所挫败。此外,如果处理多个案例的代码看起来非常相似,那么剪切和粘贴可能会导致维护问题。如果您有很多代码,但可以通过算法将它们分成几组,您可以考虑使用多个/嵌套的 switch 语句,例如,而不是:

switch (code) {
    case 0x0001: ...
    case 0x0002: ...
    ...
    case 0x8001: ...
    case 0x8002: ...
    ...
}

你可能会使用:

if (code & 0x8000) {
    code &= ~0x8000;
    switch (code) {
        case 0x0001: ... // actually 0x8001
        case 0x0002: ... // actually 0x8002
        ...
    }
}
else {
    switch (code) {
        case 0x0001: ...
        case 0x0002: ...
        ...
    }
}

许多语言解释器以这种方式解码操作码,因为单字节操作码可能将附加信息打包到不同的位中,并且转录所有可能的组合及其处理程序将是重复且脆弱的。另一方面,过多的位修饰可能会破坏编译器的任何优化并适得其反。

除非您确定这是一个真正的性能瓶颈,否则我会避免过早的优化:无论采用哪种方式都会让您觉得相当健壮且可以快速实施。如果您的应用程序运行得太慢,请对其进行分析并进行相应的优化。

【讨论】:

    【解决方案4】:

    switch 语句会更快,但如果这不是您的应用程序性能瓶颈,您不应该真正关心它。

    寻找使您的代码更易于长期维护的因素。

    您的样本太短,无法在这方面做出任何有意义的判断。

    【讨论】:

      【解决方案5】:

      我本人偏爱查找表,因为在我看来异常长的 switch 语句会混淆代码和数据之间的分离。我还认为表格更适合未来的更改和维护。

      当然,恕我直言。

      【讨论】:

        【解决方案6】:

        我建议使用静态的、恒定的、成对的表。这是查找表的另一种形式:

        struct Table_Entry
        {
            int code;
            int value;
        };
        
        static const Table_Entry  lookup_table[] =
        {
          {1, 10},
          {2, 15},
          {3, 13},
        };
        
        static const unsigned int NUM_TABLE_ENTRIES =
            sizeof(lookup_table) / sizeof(lookup_table[0]);
        

        这样做的好处是表是在编译时生成的,不像std::map 必须在运行时初始化。如果数量很大,您可以使用std::lower_bound 查找条目,前提是表格已订购。

        另一个好处是这种技术是数据驱动的。数据可以在不更改搜索引擎的情况下更改。代码或流程的更改可能需要认真的回归测试,但数据更改可能不需要; YMMV。

        这类似于编译器可能生成的内容。

        【讨论】:

          【解决方案7】:

          如果您可以使用 tr1,您可以使用 unordered_map 对值进行散列(散列整数也可以非常快),这将使大多数查找保持恒定时间。

          但是,除非您有分析数据表明这是您程序中的瓶颈,否则请以从设计角度来看最有意义的方法对其进行编码。

          【讨论】:

            【解决方案8】:

            如果您所做的只是分配一个值,我会说 map。我的原因是它可以在不更改代码的情况下进行扩展,而您的 switch 语句则不是。

            顺便说一句,enum 怎么样?

            【讨论】:

            • 无法通过枚举将整数转换为整数。
            【解决方案9】:

            我认为switch-case结构的生成代码会变得很大,如果“代码”的数量变大了,我认为stl::map更合适。

            【讨论】:

              【解决方案10】:

              通常我会建议一个映射或查找数组,甚至可能是一些混合查找怪物(当然,假设您正在优化速度或代码大小而不是可读性),但值得注意的是新版本的GCC 足够聪明,可以将这样的 switch/case 分配替换为查找表。虽然这对于完全稀疏的键来说不是很好,但如果你的键是成簇的,可能是这样的: 0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 193, 194, 195, 196, 197, 198...

              当然,如果您可能喜欢一些算术来进行转换,那就更好了(通常)。在做这样的事情时,永远不要忽视移位、交换字节顺序或更传统的数学。

              【讨论】:

                【解决方案11】:

                unordered_map 可能会更适合这里,因为它使用哈希表,查找速度会比 switch 更快。

                【讨论】:

                  【解决方案12】:
                  • 将整数读入数组/向量
                  • 对数组/向量进行排序
                  • 在底层数组上使用 bsearch

                  【讨论】:

                  • 为什么不直接使用 std::map 呢?性能保证相同,代码更简单。
                  • 更小的内存占用 == 更好的局部性 == 更好的性能
                  猜你喜欢
                  • 2018-01-24
                  • 2011-03-22
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-11-02
                  • 1970-01-01
                  • 2020-06-04
                  • 2021-07-24
                  相关资源
                  最近更新 更多