![](/img/trans.png)
[英]Switch statement with automatic break at each case step in C++
[英]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语句?
笔记:
谢谢!
编辑1
更多说明:
编辑2/3
我决定在这里做一个补充,以更好地解释我的应用程序以及为什么我更喜欢某些解决方案
目前,在我的开关内部我有一个看起来像这样的函数调用(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中只有两个成员函数:
前两个解决方案似乎是等效的,而基于开关的解决方案速度要快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=0
, B=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::map
或switch
:
使用函数指针数组和枚举作为函数指针数组的索引!
#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=42
比41
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.