当前位置: 首页 > 知识库问答 >
问题:

C++自动生成switch语句

贺高杰
2023-03-14

请考虑以下代码

#include <iostream>

enum MyEnum{
    A,    
    B,
    END
};

template <int N>
class Trait {};

template<>
class Trait<A> {
    public:
        static int funct(int i) {return i*3;}
};

template<>
class Trait<B> {
    public:
        static int funct(int i) {return i*24;}
};


using namespace std;

int main(){
    int i = 1;
    switch(i){
        case A: cout << Trait<A>::funct(i) << endl; break;
        case B: cout << Trait<B>::funct(i) << endl; break;
    }   
} 

它将在屏幕上打印24。

现在假设我在枚举中有更多的值,并且我定义了类特征的所有相应模板特化。

#include <iostream>

#define REPEAT(N, macro) REPEAT_##N(macro)
#define REPEAT_0(macro)
#define REPEAT_1(macro) REPEAT_0(macro) macro(0)
#define REPEAT_2(macro) REPEAT_1(macro) macro(1)
#define REPEAT_3(macro) REPEAT_2(macro) macro(2)
#define REPEAT_4(macro) REPEAT_3(macro) macro(3)
// etc...

// enum and class definitions

int main(){
   #define MY_MACRO(N) case N: cout << Trait<N>::funct(i) << endl; break;

   switch(i){
      REPEAT(2, MY_MACRO)
   }
}
REPEAT(END, MY_MACRO)

备注:

  • 我必须使用它的情况要复杂得多,自动操作会非常有帮助。
  • 使用switch语句对我来说很重要,因为可以实现速度(速度对我的应用程序至关重要)。

谢谢!

    null

>

  • 在我的实际应用程序中,枚举包含几乎50个不同的值,它将在将来被扩展(希望由其他人扩展)。枚举包含连续值。
  • 类“trait”有多个成员函数(当前为5个)。此外,我需要在5个不同的文件中使用所有这些。如果我不使用自动生成所需内容的方法,我就会多次编写基本相同的代码。
  • 性状的成员函数一直以相同的方式使用。
  • 当前,在我的交换机中,我有一个函数调用,如下所示(in1、in2和out都是通过引用双重传递的,前两种情况是const)。

    案例A:trait::funct(in1,in2,out);断裂;

    为什么我喜欢模板?

    template <int N>
    class Trait {
        public:
            static int funct1(int i){static_assert(N!=N, "You forgot to define funct1");}
            static int funct2(int i){static_assert(N!=N, "You forgot to define funct2");}
    };
    

    使用Jarod42建议的基于C++11特性的方法,我可以避免维护很长的函数指针数组,这会导致错误。

    速度测试

    到目前为止,我使用了3个解决方案,但在Trait中仅使用了两个成员函数:

    • JAROD42建议的解决方案
    • 由nndru和ali建议的函数指针的简单数组
    • 带有返回宏的switch语句

    前两种解决方案似乎相当,而基于交换机的方案则要快5倍。我使用了带有标志-O3的GCC4.6.3版本。

  • 共有1个答案

    闻飞跃
    2023-03-14

    正如您所说,枚举是连续的。在这种情况下,您不需要任何模板或std::mapswitch:

    只需使用函数指针数组和枚举作为函数指针数组的索引!

    #include <cassert>
    #include <cstdio>
    
    enum {
      A,
      B,
      SIZE
    };
    
    int A_funct(int i) { return 3*i; }
    
    int B_funct(int i) { return 24*i; }
    
    typedef int (*enum_funct)(int );
    
    enum_funct map[] = { A_funct, B_funct };
    
    // In C++11 use this:
    //static_assert( sizeof(map)/sizeof(map[0])==SIZE , "Some enum is missing its function!");
    
    int main() {
      assert(sizeof(map)/sizeof(map[0])==SIZE && "Some enum is missing its function!");
      int i = 1;
      std::printf("case A prints %d\n", map[A](i) );
      std::printf("case B prints %d\n", map[B](i) );
    }
    

    更新:根据您的评论:

    好了,现在我明白了维修方面的顾虑了。

    我相信,只有使用某种源代码生成方法,或者使用宏,或者编写自己的源代码生成器,才能实现这一点(无论使用函数指针数组还是开关方法)。您还必须制定一些命名约定,以便能够自动生成函数指针数组(或switch方法中的case语句中的代码)。

    由于您没有指定,所以我只是自己编了一个命名约定。如果您对宏很熟悉,下面是我通过对示例进行一些无意识的编辑,与Boost预处理器库一起黑掉的内容:

    #include <boost/preprocessor/repetition.hpp>
    
    #define ENUM_SIZE 2
    
    #define ENUM(z, n, unused) e##n,
    enum { 
      BOOST_PP_REPEAT(ENUM_SIZE, ENUM, ~)  
      SIZE
    };
    #undef ENUM
    
    int fA_e0(int i) { return 3*i; }
    int fA_e1(int i) { return 24*i; }
    
    int fB_e0(int i) { return 32*i; }
    int fB_e1(int i) { return  8*i; }
    
    typedef int (*enum_funct)(int );
    
    #define MAP(z, n, case) f ## ##case ## _e##n,
    
    enum_funct map_A[] = {
      BOOST_PP_REPEAT(ENUM_SIZE, MAP, A)
    };
    
    enum_funct map_B[] = {
      BOOST_PP_REPEAT(ENUM_SIZE, MAP, B)
    };
    
    #undef MAP
    

    以下是预处理器解析这些宏(g++-e myfile.cpp)后得到的结果:

    enum { e0, e1, SIZE };
    
    [...]
    
    typedef int (*enum_funct)(int );
    
    enum_funct map_A[] = {
      fA_e0, fA_e1,
    };
    
    enum_funct map_B[] = {
      fB_e0, fB_e1,
    };
    

    因此,正如您所看到的,如果您指定了自己的命名约定,就可以自动生成映射(函数指针数组)。文档很好。

    但是,如果我是你,我会编写自己的源代码生成器。我将指定一个简单的文本文件格式(一行中的键-值对,用空白分隔),并编写我自己的工具来从这个简单的文本文件生成所需的C++源文件。然后,构建系统将在预构建步骤中调用我的源代码生成器工具。这样,您就不用再乱弄宏了。(顺便说一下,我为自己编写了一个测试框架,为了避免C++中反射的不足,我使用了自己的源代码生成器。真的没有那么难。)

    前两种解决方案似乎相当,而基于交换机的方案则要快5倍。我使用了带有标志-O3的GCC4.6.3版本。

    我必须看到您的源代码,生成的程序集,以及您是如何测量时间的,以便了解这是如何发生的。

    所以我也做了自己的速度测试。因为它会混淆这个答案,这里有源代码:开关方法和函数指针数组方法。

    (我在编译器和链接器中都使用了链接时间优化(-o3-flto标志),我只能推荐它;它提供了很好的性能提升(在我高达2.5X的代码中),您需要做的唯一事情是传递一个额外的标志。但是,在您的例子中,代码非常简单,它没有改变任何东西。如果您希望尝试它:链接时间优化要么不可用,要么仅在gcc 4.6.3中进行试验。)

    从您的评论来看:

    我做了一些新的实验,一步一步地遵循您的基准方法,但是使用switch语句仍然得到了更好的结果(当枚举大小为150时,开关的速度仍然是使用指针的解决方案的两倍)。[...]在使用我的代码进行的测试中,switch方法的性能总是更好。我也用你的代码做了一些实验,得到了和你一样的结果。

    我查看了生成的汇编代码,至少有5个函数(5cases)。如果我们至少有这么多函数,粗略地说,会发生的情况是编译器将开关方法转变为函数指针方法,这有一个显著的缺点。即使在最好的情况下,开关在分派到要调用的函数时,与手工编码的函数指针数组方法相比,总是要经过一个额外的分支(整数比较后可能跟着一个跳转)。这个额外的分支属于default:标签,即使在C++代码中故意省略它,也会生成该标签;没有办法阻止编译器为此生成代码。(如果您最多有4个case,并且所有4个函数调用都可以内联,那么情况就不一样了;但是您已经有50个case,所以没关系。)

    除此之外,使用开关方法,还会生成附加的(冗余的)指令和页签,与case:标签处的代码相对应。这可能会增加缓存丢失。因此,在我看来,开关总是不如函数指针方法,如果您有几个以上的情况(我的机器上有5个情况)。这也是安德烈·亚历山大雷斯库在他的讲话中所说的;他给出了大约7例的限制。

    至于为什么您的速度测试显示了相反的结果:这类速度测试总是不可靠的,因为它们对对齐和缓存非常敏感。然而,在我的原始测试中,开关方法总是比函数指针数组稍差,这与我上面对汇编代码的分析是一致的。

    函数指针数组的另一个优点是它可以在运行时构建和更改;这是switch方法无法实现的。

    奇怪的是,我获得的函数指针数组的速度根据枚举大小而变化(我希望它大致是恒定的)。

    随着枚举大小的增长,您有更多的函数,更有可能发生指令缓存丢失。换句话说,如果你有更多的功能,程序应该运行得稍微慢一点。(在我的机器上是这样的。)当然,整个过程是随机发生的,因此会有很大的偏差,如果enum_size=42的运行速度比41快,不要感到惊讶。如前所述,对齐增加了额外的噪声。

     类似资料:
    • C# 中的 switch 语句有些类似于《 if else if 语句》,都可以根据表达式执行某个的语句块,其语法格式如下: switch(表达式){     case value1:     //表达式的值为 value1 时,要执行的代码         break;     case value2:     //表达式的值为 value2 时,要执行的代码         break;   

    • C++ 判断 一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。 语法 C++ 中 switch 语句的语法:switch(expression){ case constant-expression : statement(s); break; // 可选的 case constant-expressio

    • C语言虽然没有限制 if else 能够处理的分支数量,但当分支过多时,用 if else 处理会不太方便,而且容易出现 if else 配对出错的情况。例如,输入一个整数,输出该整数对应的星期几的英文表示: 运行结果: Input integer number:3↙ Wednesday 对于这种情况,实际开发中一般使用 switch 语句代替,请看下面的代码: 运行结果: Input integ

    • C++ 判断 您可以把一个 switch 作为一个外部 switch 的语句序列的一部分,即可以在一个 switch 语句内使用另一个 switch 语句。即使内部和外部 switch 的 case 常量包含共同的值,也没有矛盾。 C++ 中的 switch 语句允许至少 256 个嵌套层次。 语法 C++ 中 嵌套 switch 语句的语法: switch(ch1) { case 'A'

    • 问题内容: 有人知道如何从1开始生成,以便下一个对象具有2,依此类推吗? 我尝试了以下方法,但不起作用: 问题答案: 您需要一个 静态的 类成员来跟踪上次使用的索引。确保还实现一个复制构造函数: 更新: 正如@JordanWhite建议的那样,您可能希望使static计数器成为 atomic ,这意味着可以安全地同时使用(即一次在多个线程中使用)。为此,将类型更改为: 增量读取和复位操作变为:

    • 很多时候 if 语句可以很好的满足我们对于分支控制的需求,但是当你要对于一系列有着相同表达式不同内容的东西分类的时应该怎么办?这正式今天要介绍的内容。 1. 语法 switch (表达式) { case 常量1: 语句1 break; case 常量2: 语句2 break; . . . defa