3

多くの場合、同等であるが番号が異なる 2 つの列挙型があり、最初の列挙型の要素を 2 番目の列挙型の要素に変換する関数とその逆の関数が必要です。これは通常、コードを作成するのが非常に面倒であり、新しい要素が追加されるたびに、変換関数と逆変換関数の両方に必ずペアリングを追加する必要があります ( DRY 原則に違反します)。

効率的なコードを生成しながら、これを行うための最もエラーが発生しにくい方法は何ですか? 実行時のルックアップが問題にならない場合は、一連のペアを作成して std::maps に配置するだけでよいため、効率的な部分について言及します。私は、ある列挙値から別の列挙値に移動する大きな switch ステートメントを手動で記述するのと同じくらいパフォーマンスの高いものを好みます。

いくつかのブースト プリプロセッサ マジックまたはいくつかのテンプレート ハッカーを使用して、ペアのリストを記述し、変換関数と逆変換関数が生成される何かを考え出すことができると確信していますが、どちらのアプローチを優先するか、またはその理由はわかりません。どちらの方法も、コンパイル時間が遅く、コンパイル エラーの診断が難しいという評判があります。それとも、まったく別のアプローチですか?

4

6 に答える 6

3

ニールが言ったように、私は自分で問題に直面したことはありません。ただし、コードセットの問題に直面しました(つまり、文字列から列挙型へのマッピング、およびその逆)。

私の最初の反応は表皮性です:DRY.

ご指摘のとおり、2 つの列挙型と翻訳を維持するには時間がかかるため、リファクタリングして 1 つだけを使用することをお勧めします。

私の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)
{
  //...
}

良いニュースは、これは可能です。私はそれを行うことさえできますが、おそらくあなたに少し作業を任せます;)

ここにいくつかの説明があります:

  • マクロの 3 番目の要素はシーケンスです。シーケンスのサイズは無制限です。
  • シーケンスの各要素は 4 タプルです。そのサイズを事前に知る必要があるため、 の繰り返しですCommon。代わりにシーケンスを使用した場合、可変数の要素を処理できます (たとえば、共通の繰り返しを避けるため...)。ただし、物事ははるかに複雑になります。
  • を参照する必要がありますBOOST_PP_SEQ_FOREACH。ここでの基本的な操作になります。
  • BOOST_PP_CATトークンの連結を処理することを忘れないでください。
  • gccオプションでは-E、プリプロセッサの出力が生成されます。便利な場合があります...
  • コメントとファイルでの使用方法を忘れないでください。そうしないと、同僚に嫌われます
于 2010-07-07T17:19:42.613 に答える
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);
}
于 2010-07-07T16:12:35.410 に答える
2

ルックアップ テーブルが機能しないのはなぜですか? なぜこの巨大な switch ステートメントを使用せざるを得ないのですか??

于 2010-07-07T16:35:06.010 に答える
1

列挙範囲が (ビットマップ インジケーターとして使用されるのではなく) 比較的密集している場合は、配列を使用してマッピングを行うことができます。コンパイラーに配列の長さを把握させてから、長さが必要なものでないかどうかをアサートできます。static_assert できるかもしれませんが、わかりません。配列を使用しているため、変換は一定時間である必要があり、コンパイラが内部でジャンプ テーブルを生成しない場合は、スイッチよりも優れている可能性があります。このコードは完全にテストされていないことに注意してください。

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;
}
于 2010-07-07T17:52:24.023 に答える
0

2 つの列挙型を使用しないことを検討してください。

これらの間に大きな違いはありません:

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) ルックアップを行うよりは少しましです。

于 2010-07-07T16:30:47.737 に答える
0

まあ、いつでも関数を作成しようとすることができます(プログラミング関数ではなく、数学関数の意味で)、1つの列挙型の数を別の列挙型に変換します。ただし、この関数は、要素を追加するたびに変更されます。

于 2010-07-07T16:01:41.147 に答える