如何以最少的代码优化翻译枚举?

时间:2010-07-07 15:46:31

标签: c++ performance enums translation adapter

通常情况下,您有两个等效但编号不同的枚举,并且您需要函数将元素从第一个枚举转换为第二个和反向的元素。编码通常非常繁琐,无论何时添加新元素,您都必须确保将配对添加到转换和反向转换功能(它违反了the DRY principle)。

什么是最不容易出错的方法仍能生成有效的代码?我提到了有效的部分,因为如果运行时查找不是问题,你可以制作一堆对并将它们放在std :: maps中。我更喜欢像手动编写从一个枚举值到另一个枚举值的大转换语句一样高效的东西。

使用一些boost预处理器魔法或一些模板hackery我敢肯定你可以想出一些你写的对列表并生成转换和反向转换函数,但我不确定哪种方法更喜欢或为什么。这两种方法都因编译时间慢而难以诊断编译错误。或者可能完全采用其他方法?

6 个答案:

答案 0 :(得分:2)

你正在寻找这样的东西吗?没有经过测试,但应该可以使用。

(关于过早优化和需要分析的标准警告适用; std :: map查找可能不是那么糟糕,巨大的切换表可能不那么好。)

枚举-impl.h:

// No include guard.
DEFINE_ENUM_PAIR(EGA_BRIGHT_RED, 12, HTML_RED, 0xff0000)
DEFINE_ENUM_PAIR(EGA_BRIGHT_BLUE, 9, HTML_BLUE, 0x0000ff)
DEFINE_ENUM_PAIR(EGA_BRIGHT_GREEN, 10, HTML_GREEN, 0x00ff00)
DEFINE_ENUM_PAIR(EGA_BLACK, 0, HTML_BLACK, 0x000000)

enums.cpp:

enum EgaColorType {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) name1 = value1,
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR
};

enum HtmlColorType {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) name2 = value2,
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR 
};

HtmlColorType ConvertEgaToHtml(EgaColorType c) {
switch (c) {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) case name1: return name2;
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR
default: assert(false);
}

EgaColorType ConvertHtmlToEga(HtmlColorType c) {
switch (c) {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) case name2: return name1;
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR
default: assert(false);
}

答案 1 :(得分:2)

为什么查找表不起作用?为什么你被迫使用这个巨大的开关语句?

答案 2 :(得分:2)

正如尼尔所说,我自己从未遇到过这个问题。我遇到了代码集的问题(即从字符串映射到枚举,然后返回)。

我的第一反应是表皮:干燥。

正如您所指出的,维护两个枚举和翻译需要时间,因此最好重构并仅使用一个。

我的第二反应是尝试删除枚举。我不喜欢枚举。也许我会对即将到来的C ++ 0x感谢他们,但是就目前而言,他们在我脑海中的价值要高于他们的价值。我更喜欢智能物品,类别等...

但是,我猜这个问题本身很有趣。因此,如果你想处理这种混乱局面,我不妨试着减轻你的负担。

模板在这里做不了什么。我已经将它们用于枚举的范围检查和字符串转换(来回)和迭代,但这就是他们所能做的。但是,正如您所怀疑的那样,Boost.Preprocessor的某些“微妙”应用是可能的。

您想写的内容:

DEFINE_CORRESPONDING_ENUMS(Server, Client,
  ((Server1, 1, Client1, 6))
  ((Server2, 2, Client2, 3))
  ((Common1, 4, Common1, 4))
  ((Common2, 5, Common2, 5))
  ((Server3, 7, Client3, 1))
);

我们希望它能够生成:

struct Server
{
  enum type { Server1 = 1, Server2 = 2, Common1 = 4, Common2 = 5, Server3 = 7 };
};

struct Client
{
  enum type { Client1 = 6, Client2 = 3, Common1 = 4, Common2 = 5, Client3 = 1 };
};

Server::type ServerFromClient(Client::type c)
{
  switch(c)
  {
  case Client1: return Server1;
  //...
  default: abort();
  }
}

Client::type ClientFromServer(Server::type s)
{
  //...
}

好消息是,这是可能的。我甚至可以做到,虽然我可能会让你稍微努力一下;)

以下是一些解释:

  • 宏的第三个元素是序列。序列的大小无限大。
  • 序列的每个元素都是4元组。您需要提前知道它的大小,因此Common的重复。如果你使用了一个序列,你可以处理可变数量的元素(例如,为了避免重复常见...),但它会使事情变得更复杂
  • 您需要查看BOOST_PP_SEQ_FOREACH,这将是此处的基本操作。
  • 不要忘记BOOST_PP_CAT来处理令牌的连接。
  • on gcc -E选项产生预处理器输出,它可能会派上用场......
  • 不要忘记评论以及如何在文件中使用,否则你的同事会讨厌你

答案 3 :(得分:1)

如果枚举范围相对密集(而不是用作位图指示符),则可以使用数组进行映射。你让编译器弄清楚数组长度,然后你可以断言长度是不是你想要的长度。你甚至可以静态地设置它,我不确定。由于您正在使用数组,因此转换应该是恒定时间,如果编译器不在内部生成跳转表,则可能比交换机更好。请注意,此代码完全未经测试。

enum A
{
    MIN_A = 1,
    A_ATT_1 = 1,
    A_ATT_2 = 2,
    A_ATT_3 = 3,
    LAST_A
};

enum B
{
    MIN_B = 2
    B_ATT_2 = 2,
    B_ATT_1 = 4,
    B_ATT_3 = 5,
    LAST_B
};

B A_to_B[] =
{
    B_ATT_1,
    B_ATT_2,
    B_ATT_3
};

// Somewhere that will always run, as desired:
assert(LAST_A - MIN_A == sizeof(A_to_B) / sizeof(A_to_B[0]);

B from_A(A in)
{
    B ret = A_to_B[in - MIN_A];
    assert(ret != LAST_B);
    return ret;
}

A B_to_A[] =
{
    A_ATT_2,
    LAST_A,
    A_ATT_1,
    A_ATT_3
};

// Somewhere that will always run, as desired:
assert(LAST_B - MIN_B == sizeof(B_to_A) / sizeof(B_to_A[0]);

A from_B(B in)
{
    A ret = B_to_A[in - MIN_B];
    assert(ret != LAST_A);
    return ret;
}

答案 4 :(得分:0)

好吧,你总是可以尝试创建一个函数(在数学函数意义上,而不是编程函数),将一个枚举的数量转换为另一个。但是,每次添加元素时,此函数都会更改。

答案 5 :(得分:0)

考虑不要使用两个枚举。

这些之间差别不大:

enum FirstSet { A=4, B=6, C=8, D=5 };
enum SecondSet { E=2, F=5, G=5, H=1 };

和此:

enum OneEnum { A, B, C, D };
enum TwoEnum { E, F, G, H };
int FirstSet[] = { 4, 6, 8, 5 };
int SecondSet[] = { 2, 5, 5, 1 };

需要更改的访问次数可能过高,但每次要转换时,这比O(n)查找要好一些。