繁体   English   中英

C ++自动生成switch语句

[英]C++ automatic generation of switch statement

请考虑以下代码

#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。

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

为了避免编写switch语句中所需的所有代码,我编写了一个REPEAT宏,它的工作方式几乎与我想要的一样:

#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语句?

笔记:

  • 我必须使用它的情况要复杂得多,自动化的东西会非常有用。
  • 由于速度可以实现(对我的应用来说速度至关重要),因此使用switch语句非常重要。

谢谢!

编辑1

更多说明:

  • 重要的是交换机的生成取决于枚举中定义的END值。

编辑2/3

我决定在这里做一个补充,以更好地解释我的应用程序以及为什么我更喜欢某些解决方案

  • 在我的实际应用程序中,枚举包含近50个不同的值,并且将来会扩展(希望由其他人)。 枚举包含连续值。
  • “Trait”类有超过1个成员函数(目前为5个)。 此外,我需要在5个不同的文件中使用所有这些。 如果我不使用自动生成我需要的方式,我最终写了很多次代码,基本上是相同的。
  • Trait的成员函数始终以相同的方式使用。
  • 目前,在我的开关内部我有一个看起来像这样的函数调用(in1,in2和out都是通过引用双重传递,const是前两种情况)。

    案例A:Trait :: funct(in1,in2,out); 打破;

为什么我喜欢模板?

考虑案例Trait有2个函数funct1和funct2。 我可以定义

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建议的一个简单的函数指针数组
  • 使用RETURN宏切换语句

前两个解决方案似乎是等效的,而基于开关的解决方案速度要快5倍。 我使用gcc版本4.6.3和标志-O3。

在C ++ 11中,您可以执行以下操作:

#if 1 // Not in C++11
#include <cstdint>

template <std::size_t ...> struct index_sequence {};

template <std::size_t I, std::size_t ...Is>
struct make_index_sequence : make_index_sequence < I - 1, I - 1, Is... > {};

template <std::size_t ... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> {};

#endif

namespace detail {

template <std::size_t ... Is>
int funct(MyEnum e, int i, index_sequence<Is...>)
{
    // create an array of pointer on function and call the correct one.
    return std::array<int(*)(int), sizeof...(Is)>{{&Trait<MyEnum(Is)>::funct...}}[(int)e](i);
}

} // namespace detail

int funct(MyEnum e, std::size_t i)
{
    return detail::funct(e, i, make_index_sequence<std::size_t(END)>());
}

注意: enum不应该有孔(所以这里A=0B=1就可以了)

以下宏可能有所帮助:

#define DYN_DISPATCH(TRAIT, NAME, SIGNATURE, ENUM, ENUM_END) \
    namespace detail { \
    template <std::size_t ... Is> \
    constexpr auto NAME(ENUM e, index_sequence<Is...>) -> SIGNATURE \
    { \
        return std::array<SIGNATURE, sizeof...(Is)>{{&TRAIT<ENUM(Is)>::NAME...}}[(int)e]; \
    } \
    } /*namespace detail */ \
    template <typename...Ts> \
    auto NAME(ENUM e, Ts&&...ts) \
        -> decltype(std::declval<SIGNATURE>()(std::declval<Ts>()...)) \
    { \
        return detail::NAME(e, make_index_sequence<std::size_t(ENUM_END)>())(std::forward<Ts>(ts)...); \
    }

然后将其用作:

    DYN_DISPATCH(Trait, funct, int(*)(int), MyEnum, END)

    // now `int funct(MyEnum, int)` can be call.

正如你所说,你的枚举是连续的。 在这种情况下,您不需要任何模板或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) );
}

更新:从您的意见:

我唯一关心的可维护性是关于明确写下5个不同的函数指针数组(如果我不自动化这个)。

好的,现在我了解维护问题。

我相信如果您使用某种源代码生成 (使用宏或编写自己的源代码生成器),您只能实现此目的(无论您使用函数指针数组还是switch方法)。 您还必须制定一些命名约定,以便可以自动生成函数指针数组(或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倍。 我使用gcc版本4.6.3和标志-O3。

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

所以我也做了自己的速度测试。 由于它会混淆这个答案,源代码在这里: switch方法函数指针数组方法。

正如我所预料的那样: switch方法更快,但只有你有一些分支。 Andrei Alexandrescu也在他的演讲中用同样的方式在C ++中快速编写快速代码 ,大约38分钟。 在我的机器上,如果枚举大小为5,则switch方法与函数指针数组方法一样快。 如果枚举大小大于5,则函数指针数组方法始终更快。 如果枚举大小为200并且有10 ^ 8个函数调用,则在我的机器上运行速度提高10%以上。 (在线代码只有10 ^ 7个函数调用,否则会超时。)

(我使用了链接时间优化( -O3 -flto标记编译器链接器),我只能推荐它;它提供了很好的性能提升(在我的代码中高达2.5倍)并且你唯一需要做的事情是传递一个额外的标志。但是,在你的情况下代码是如此简单,它没有改变任何东西。如果你想尝试它:链接时间优化要么不可用或只在gcc 4.6.3实验。)


从你的评论:

我一步一步地按照你的基准测试方法进行了新的实验,但是我仍然用switch语句得到了更好的结果(当枚举大小为150时,开关仍然几乎是带指针的解决方案的两倍)。 [...]在使用我的代码进行的测试中,切换方法总是更好。 我还对你的代码进行了一些实验,我得到了同样的结果。

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

除此之外,通过switch方法,生成附加(冗余)指令和填充,对应于case:的代码case:标签。 这可能会增加您的缓存未命中。 因此,正如我所看到的,如果您有多个案例(我的机器上有5个案例),那么switch总是不如函数指针方法。 这就是Andrei Alexandrescu在他的演讲中所说的 ; 他给出了约7例的限制。

至于你的速度测试表明相反的原因:这种速度测试总是不可靠的,因为它们对齐和缓存非常敏感。 然而,在我的原始测试中,切换方法总是比函数指针数组稍差,这与我上面对汇编代码的分析一致。

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

奇怪的是,我用函数指针数组得到的速度会根据枚举大小而改变(我希望它大致保持不变)。

随着枚举大小的增加,您将拥有更多功能,并且更有可能发生指令缓存未命中。 换句话说,如果你有更多的功能,程序应该运行稍慢。 (它在我的机器上。)当然整个事情是随机发生的,所以会有很大的偏差,如果ENUM_SIZE=4241 ENUM_SIZE=42 ,不要感到惊讶。 如前所述,对齐会为此增加额外的噪音。

您根本不需要模板来执行此操作。 更像是老X宏

#define MY_ENUM_LIST VAL(A) VAL(B)

// define an enum
#define VAL(x) x,
enum MyEnum { MY_ENUM_LIST END };
#undef VAL

// define a few functions doing a switch on Enum values  

void do_something_with_Enum (MyEnum value, int i)
{
   switch (value)
   {
      #define VAL(N) case N: std::cout << Trait<N>::funct(i) << std::endl; break;
      MY_ENUM_LIST
      #undef VAL
   }
}

int do_something_else_with_Enum (MyEnum value)
{
   switch (value)
   {
      #define VAL(x) case x: yet_another_template_mayhem(x);
      MY_ENUM_LIST
      #undef VAL
   }
}

我已经浪费了足够的时间。 如果您认为模板是解决方案,只需将您的问题更改为“仅限模板专家,预处理器不够好”或其他内容。

你不会是第一个在无用模板上浪费时间的人。 许多人为解决不存在的问题提供臃肿无用的解决方案。

此外,您假设开关比函数指针数组更快是值得商榷的。 这完全取决于枚举中的值的数量以及case语句中代码的可变性。

现在,如果优化不是一个大问题,你可以简单地使用虚方法来专门化你的枚举选择的任何对象的行为,让编译器为你处理整个“自动切换”的东西。

这种方法的唯一好处是避免重复代码,如果您的对象足够相似,使您认为您将比以特殊方式处理它们的编译器做得更好。

您似乎要求的是优化未知代码模式的通用解决方案,这是一个矛盾的术语。

编辑:感谢Jarod42清理示例。

看起来你想要与每个函数关联和整数id,并通过id查找函数。

如果您的id是顺序的,那么您可以拥有一个由该id索引的函数指针数组,这将为您提供O(1)查找复杂性,例如:

typedef int Fn(int);

enum FnId {
    A,
    B,
    FNID_COUNT
};

int fn_a(int);
int fn_b(int);

Fn* const fns[FNID_COUNT] = {
    fn_a,
    fn_b
};

int main() {
    fns[A](1); // invoke function with id A.
}

如果id不是顺序的,你仍然可以有一个{id, function_ptr}元组的排序数组,并对其进行二进制搜索,O(lg(N))查找复杂度。

这些都不需要宏或模板。

对于数字(数据库)类型标识符,我有一个包含标识符的模板。 通过可变参数模板的调度调用具有匹配类型特征的仿函数:

#include <iostream>
#include <stdexcept>

// Library
// =======

class TypeIdentifier
{
    public:
    typedef unsigned Integer;

    enum Value
    {
        Unknown,
        Bool,
        Int8,
        UInt8,
        Int16,
        UInt16,
        Int32,
        UInt32,
        Int64,
        UInt64,
        Float,
        Double,
        String,
        LargeObject,
        Date,
        Time,
        DateTime
    };

    template <Value ...Ids>  struct ListType {};
    typedef ListType<
        Bool,
        Int8,
        UInt8,
        Int16,
        UInt16,
        Int32,
        UInt32,
        Int64,
        UInt64,
        Float,
        Double,
        String,
        LargeObject,
        Date,
        DateTime,
        // Always the last value:
        Unknown
    >
    List;

    public:
    TypeIdentifier(Integer value = Unknown)
    :   m_id(value)
    {}

    Integer id() const { return m_id; }

    /// Involve a functor having a member function 'Result apply<Traits>()'.
    template<typename Functor>
    typename Functor::result_type dispatch(const Functor&);

    private:
    Integer m_id;
};

template<TypeIdentifier::Value I>
struct TypeTraits
{
    static constexpr TypeIdentifier::Value Id = I;
    static constexpr bool is(TypeIdentifier::Integer id) { return (Id == id); }
    static bool is(TypeIdentifier type_identifier) { return (Id == type_identifier.id()); }

    // And conversion functions
};


namespace TypeIdentifierDispatch {

template <typename Functor, TypeIdentifier::Value I, TypeIdentifier::Value ... Ids> struct Evaluate;

template <typename Functor>
struct Evaluate<Functor, TypeIdentifier::Unknown> {
    static typename Functor::result_type
    apply(TypeIdentifier::Integer id, const Functor&) {
        throw std::logic_error("Unknown Type");
    }
};

template <typename Functor, TypeIdentifier::Value I, TypeIdentifier::Value ... Ids>
struct Evaluate {
    static typename Functor::result_type
    apply(TypeIdentifier::Integer id, const Functor& functor) {
        if(TypeTraits<I>::is(id))
            return functor.template apply<TypeTraits<I>>();
        else return Evaluate<Functor, Ids...>::apply(id, functor);
    }
};

template <typename Functor, TypeIdentifier::Value ... Ids>
inline typename Functor::result_type
evaluate(TypeIdentifier::Integer id, const Functor& functor, TypeIdentifier::ListType<Ids...>)
{
    return Evaluate<Functor, Ids...>::apply(id, functor);
}

} // namespace TypeIdentifierDispatch

template<typename Functor>
inline
typename Functor::result_type TypeIdentifier::dispatch(const Functor& functor) {
    return TypeIdentifierDispatch::evaluate(id(), functor, TypeIdentifier::List());
}



// Usage
// =====

struct Print {
    typedef void result_type;

    template <typename Traits>
    result_type apply() const {
        std::cout << "Type Identifier: " << Traits::Id << '\n';
    }
};

inline void print_identifier(unsigned value) {
    TypeIdentifier(value).dispatch(Print());
}


int main ()
{
    print_identifier(TypeIdentifier::String);
    return 0;
}

向库中添加新类型需要调整TypeIdentfier并(可能)添加专门的TypeTraits。

请注意,枚举值可以是任意的。

使用递归模板,您可以自动生成相当于的构造

   if (i = A)
      Trait<A>::funct(i);
   else if (i = B)
      Trait<B>::funct(i);

我认为它的性能类似于switch语句。 您的原始示例可以重写如下。

#include <iostream>

using namespace std;

enum MyEnum {
   A,
   B,
   END
};

template <MyEnum N>
class Trait 
{ public:
   static int funct(int i)
   { 
      cout << "You forgot to define funct" << i;
      return i; 
   } 
};

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; }
};

template <MyEnum idx>
int Switch(const MyEnum p, const int n)
{
   return (p == idx) ? Trait<idx>::funct(n) : Switch<(MyEnum)(idx - 1)>(p, n);
}

template <>
int Switch<(MyEnum)(0)>(const MyEnum p, const int n)
{
   return Trait<(MyEnum)(0)>::funct(n);
}

int funct(MyEnum n)
{
   return Switch<END>(n, n);
}

int main() {
   MyEnum i = B;
   cout << funct(i);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM