【问题标题】:How to make efficient C++ jump table?如何制作高效的C++跳转表?
【发布时间】:2016-05-17 07:54:51
【问题描述】:

我是 C++ 的初学者,我已经实现了以下简单的跳转表,但想知道我是否以正确的方式进行操作。无论如何我可以改进以下代码吗?

以下代码使用字典(我来自 C# 背景)来存储函数的指针。

#include <cstdio>
#include <iostream>
#include <string>
#include <unordered_map>

using namespace std;

void Zero() { printf("Zero\n"); }
void One() { printf("One\n"); }   
void Two() { printf("Two\n"); }
void Three() { printf("Three\n"); }

string prompt()
{
    printf("Enter number from 0 to 3 or q to quit:\n");
    string line;
    getline(cin, line);

    return line;
}

int main(int argc, const char * argv[]) {

    unordered_map<string, void(*)()> map;

    map["0"] = Zero;
    map["1"] = One;
    map["2"] = Two;
    map["3"] = Three;

    while (true) {
        string c = prompt();
        if (c == "q") break;

        map[c]();
    }
    return 0;
}

【问题讨论】:

  • 效率如何?
  • m[c] 不存在时怎么样 - 即用户输入了不在菜单上的内容
  • 关于改进工作代码的问题更适合Code Review
  • 基本思路是好的,虽然执行在几个不相关的方面存在缺陷。

标签: c++ arrays dictionary


【解决方案1】:

switch 语句怎么样?

switch (c) {
   case 0:
      printf("Zero\n"); break;
   case 1:
      printf("One\n"); break;
   case 2:
      printf("Two\n"); break;
   case 3:
      printf("Three\n"); break;
   default:
      break;
}

【讨论】:

  • 这很可能是最佳解决方案。当然,优化者有机会看到它。
  • 你应该在 switch 语句中使用 '0' 而不是 0。
【解决方案2】:

如果不采用 switch 解决方案,您将无法让代码“更快”,因为它打破了拥有一组函数的最初想法。如果您只使用 '0' => '9'、'a' => 'z' 之类的“字符”,则可以避开字符串所需的内存分配,还可以使用 initializer_list 初始化地图,并且如果可行的话,你也可以将这样的数组设为静态。

如果有帮助,这里是我的“优化”代码。

inline char prompt() //this function will probably 900% be inlined even if you don't specify the inlike keyword
{
    printf("Enter number from 0 to 3 or q to quit:\n");
    char v;
    while (!(std::cin >> v)); //Just to make sure we get valid input
    return v;
}

int main()
{
    static const std::unordered_map<char, void(*)()> mymap = 
    {
        { '0' , Zero },
        { '1' , One },
        { '2' , Two },
        { '3' , Three }
    };

    while(1)
    {
        auto it = mymap.find(prompt());

        // Without this check, your program will crash if input is invalid.
        if (it != mymap.end()) 
        {
            it->second();
            break;
        }
    }

    return 0;
}

【讨论】:

  • 我担心“字符串”,如果我应该使用“字符”,你修复了它,但为什么是“内联”?
  • 我在评论中解释了它。它不是必需的,但它是一种让阅读您的代码的人知道该函数应该被内联的方法。这些天来, inline 关键字没有多大作用,因为编译器总是有最后一个字。 __forceinline,顾名思义,是不同的。这实际上强制编译器内联函数(但在某些情况下,由于技术原因编译器无法内联函数),因此 __forceinline 函数在某些情况下可能不会被内联。 __forceinline 是 MSVC 特定的顺便说一句。
【解决方案3】:

请为您的效率案例提供更多详细信息。你的意思是内存/CPU周期/直通? 根据您的代码:

  • 不易出错(使用auto it = map.find(key); 函数搜索并检查输出it != map.end() 值,因此不会创建新元素)
  • 对于字符串键类型已经足够了
  • std::function&lt;void()&gt;替换函数指针,让你的case变得更灵活

它对你可以自定义散列函数和自定义散列表实现的更底层控制而言。 在某些数据上,考虑std::map 或排序std::vector 作为一个选项可能会很有用。

【讨论】:

    【解决方案4】:

    由于静态查找快速,无论编译器如何,这都会执行得非常好。跳转表因编译器而异。我会使用以下代码,可能有些人会反对这一点,因为global 不好。但在评论之前,请先评估一下

    string prompt()
    {
         printf("Enter number from 0 to 3 or q to quit:\n");
         string line;
         getline(cin, line);
    
         return line;
    }
    
    enum Choice = {ZERO = 0, ONE, TWO, THREE};
    
    static char *choice_str[] = {
         "Zero",
         "One",
         "Two",
         "Three"
    };
    
    int main(int argc, const char * argv[]) {
        while (true) {
            string c = prompt();
            if (c == "q") 
            {
                break;
             }
             else {
                  assert(atoi(c) >= Choice::ZERO && atoi(c) <=Choice::THREE);
                  printf("%s\n", choice_str[atoi(c)]);
             }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-26
      • 2012-04-13
      相关资源
      最近更新 更多