45

C++ の組み込み列挙型がタイプセーフでないことはよく知られています。タイプセーフな列挙型を実装するクラスがどこで使用されているのか疑問に思っていました...私自身は次の「自転車」を使用していますが、やや冗長で制限があります。

typesafenum.h:

struct TypesafeEnum
{
// Construction:
public:
    TypesafeEnum(): id (next_id++), name("") {}
    TypesafeEnum(const std::string& n): id(next_id++), name(n) {}

// Operations:
public:
    bool operator == (const TypesafeEnum& right) const;
    bool operator != (const TypesafeEnum& right) const;
    bool operator < (const TypesafeEnum& right) const;

    std::string to_string() const { return name; }

// Implementation:
private:
    static int next_id;
    int id;
    std::string name;
};

typesafenum.cpp:

int TypesafeEnum::next_id = 1;

bool TypesafeEnum::operator== (const TypesafeEnum& right) const 
{ return id == right.id; }

bool TypesafeEnum::operator!= (const TypesafeEnum& right) const 
{ return !operator== (right); }

bool TypesafeEnum::operator< (const TypesafeEnum& right) const  
{ return id < right.id; }

使用法:

class Dialog 
{
 ...
    struct Result: public TypesafeEnum
    {
        static const Result CANCEL("Cancel");
        static const Result OK("Ok");
    };


    Result doModal();
 ...
};

const Dialog::Result Dialog::Result::OK;
const Dialog::Result Dialog::Result::CANCEL;

追加: 要件についてもっと具体的であるべきだったと思います。私はそれらを要約しようとします:

優先度 1: enum 変数を無効な値に設定することは、例外なく不可能 (コンパイル時エラー) であるべきです。

優先度 2: enum 値と int の間の変換は、単一の明示的な関数/メソッド呼び出しで可能にする必要があります。

優先度 3: 可能な限りコンパクトでエレガントで便利な宣言と使用法

優先度 4: enum 値と文字列との変換。

優先度 5: (あると便利) enum 値を反復処理できる可能性。

4

11 に答える 11

42

私は現在、Boost Vault (ファイル名enum_rev4.6.zip) からの Boost.Enum 提案をいじっています。Boost に含めるために正式に提出されたことはありませんが、そのまま使用できます。(ドキュメントは不足していますが、明確なソース コードと優れたテストによって補われています。)

Boost.Enum では、次のように列挙型を宣言できます。

BOOST_ENUM_VALUES(Level, const char*,
    (Abort)("unrecoverable problem")
    (Error)("recoverable problem")
    (Alert)("unexpected behavior")
    (Info) ("expected behavior")
    (Trace)("normal flow of execution")
    (Debug)("detailed object state listings")
)

そして、これを自動的に展開します:

class Level : public boost::detail::enum_base<Level, string>
{
public:
    enum domain
    {
        Abort,
        Error,
        Alert,
        Info,
        Trace,
        Debug,
    };

    BOOST_STATIC_CONSTANT(index_type, size = 6);

    Level() {}
    Level(domain index) : boost::detail::enum_base<Level, string>(index) {}

    typedef boost::optional<Level> optional;
    static optional get_by_name(const char* str)
    {
        if(strcmp(str, "Abort") == 0) return optional(Abort);
        if(strcmp(str, "Error") == 0) return optional(Error);
        if(strcmp(str, "Alert") == 0) return optional(Alert);
        if(strcmp(str, "Info") == 0) return optional(Info);
        if(strcmp(str, "Trace") == 0) return optional(Trace);
        if(strcmp(str, "Debug") == 0) return optional(Debug);
        return optional();
    }

private:
    friend class boost::detail::enum_base<Level, string>;
    static const char* names(domain index)
    {
        switch(index)
        {
        case Abort: return "Abort";
        case Error: return "Error";
        case Alert: return "Alert";
        case Info: return "Info";
        case Trace: return "Trace";
        case Debug: return "Debug";
        default: return NULL;
        }
    }

    typedef boost::optional<value_type> optional_value;
    static optional_value values(domain index)
    {
        switch(index)
        {
        case Abort: return optional_value("unrecoverable problem");
        case Error: return optional_value("recoverable problem");
        case Alert: return optional_value("unexpected behavior");
        case Info: return optional_value("expected behavior");
        case Trace: return optional_value("normal flow of execution");
        case Debug: return optional_value("detailed object state listings");
        default: return optional_value();
        }
    }
};

あなたが挙げた5つの優先事項すべてを満たしています。

于 2009-01-13T13:51:09.080 に答える
18

良い妥協方法は次のとおりです。

struct Flintstones {
   enum E {
      Fred,
      Barney,
      Wilma
   };
};

Flintstones::E fred = Flintstones::Fred;
Flintstones::E barney = Flintstones::Barney;

バージョンと同じ意味でタイプセーフではありませんが、標準の列挙型よりも使用法が優れており、必要なときに整数変換を利用できます。

于 2008-10-20T05:09:03.863 に答える
13

私はC++0x の typesafe enumsを使用しています。to/from 文字列機能を提供するいくつかのヘルパー テンプレート/マクロを使用します。

enum class Result { Ok, Cancel};
于 2009-01-13T14:09:42.133 に答える
6

私はしません。ほとんどメリットがないのに、オーバーヘッドが多すぎます。また、シリアル化のために列挙をさまざまなデータ型にキャストできることは、非常に便利なツールです。C++ が既に十分に優れた実装を提供しているにもかかわらず、「タイプ セーフ」な列挙がオーバーヘッドと複雑さに見合うだけの価値があるインスタンスを見たことがありません。

于 2008-10-20T05:04:33.933 に答える
2

私は個人的にtypesafeenumイディオムの適応バージョンを使用しています。編集で述べた5つの「要件」すべてを提供しているわけではありませんが、とにかくそれらのいくつかに強く反対します。たとえば、Prio#4(値から文字列への変換)が型の安全性とどのように関係しているかはわかりません。個々の値のほとんどの時間文字列表現は、とにかく型の定義とは別にする必要があります(単純な理由でi18nを考えてください)。Prio#5(iteratio、オプション)は、列挙型で自然に発生することを望んでいる最も優れたものの1つであるため、リクエストで「オプション」として表示されるのは悲しいと思いましたが、/などの個別の反復システムbeginend関数またはenum_iterator。これにより、STLおよびC ++11foreachとシームレスに連携します。

enumOTOHこの単純なイディオムは、ほとんどの場合、より多くの型情報でsをラップするだけであるという事実のおかげで、Prio#3 Prio#1をうまく提供します。言うまでもなく、これは非常に単純なソリューションであり、ほとんどの場合、外部の依存関係ヘッダーを必要としないため、持ち運びが非常に簡単です。また、列挙のスコープをa-la-C++11にするという利点もあります。

// This doesn't compile, and if it did it wouldn't work anyway
enum colors { salmon, .... };
enum fishes { salmon, .... };

// This, however, works seamlessly.
struct colors_def { enum type { salmon, .... }; };
struct fishes_def { enum type { salmon, .... }; };

typedef typesafe_enum<colors_def> colors;
typedef typesafe_enum<fishes_def> fishes;

enumソリューションが提供する唯一の「穴」は、値を直接使用すると暗黙的な変換を強制するため、異なるタイプ(またはanenumとint)のsが直接比較されるのを妨げないという事実に対処しないことです。にint

if (colors::salmon == fishes::salmon) { .../* Ooops! */... }

しかし、これまでのところ、このような問題は、コンパイラとのより良い比較を提供するだけで解決できることがわかりました。たとえば、2つの異なるenum型を比較す​​る演算子を明示的に提供し、それを強制的に失敗させます。

// I'm using backports of C++11 utilities like static_assert and enable_if
template <typename Enum1, typename Enum2>
typename enable_if< (is_enum<Enum1>::value && is_enum<Enum2>::value) && (false == is_same<Enum1,Enum2>::value) , bool >
::type operator== (Enum1, Enum2) {
    static_assert (false, "Comparing enumerations of different types!");
}

これまでのところコードを壊しているようには見えず、他に何もせずに特定の問題に明示的に対処しているようですが、そのようなことが「すべき」ことであるかどうかはわかりません(干渉する可能性があります)enumsすでに他の場所で宣言されている変換演算子に参加しています。これについてのコメントを喜んで受け取ります)。

これを上記のタイプセーフなイディオムと組み合わせると、enum classあまり曖昧なことをすることなく、人間性(読みやすさと保守性)においてC++11に比較的近いものが得られます。そして、私はそれをするのが楽しかったことを認めなければなりません、私が実際にコンパイラにsを扱っているかどうかを尋ねるenumことを考えたことはありませんでした...

于 2012-08-08T02:08:05.700 に答える
2

私の見解では、あなたは問題を発明し、それに解決策を当てはめているということです。値の列挙のために精巧なフレームワークを行う必要はないと思います。値を特定のセットのメンバーのみにすることに専念している場合は、一意のセット データ型のバリアントをハックすることができます。

于 2008-10-20T15:09:24.087 に答える
1

enumJavaは従うのに適したモデルになると思います。基本的に、Java フォームは次のようになります。

public enum Result {
    OK("OK"), CANCEL("Cancel");

    private final String name;

    Result(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Java アプローチの興味深い点は、OKCANCELが不変のシングルトン インスタンスでResultあることです (ご覧のメソッドを使用)。のインスタンスをこれ以上作成することはできませんResult。それらはシングルトンであるため、ポインター/参照で比較できます---非常に便利です。:-)

ETA: Java では、手動でビットマスクを作成する代わりに、 を使用しEnumSetてビット セットを指定します (Setインターフェイスを実装し、セットのように動作しますが、ビットマスクを使用して実装します)。手書きのビットマスク操作よりもはるかに読みやすい!

于 2008-10-20T05:08:13.113 に答える
1

別のトピックで、ここでこれに答えました。これは、元の列挙型定義を変更する必要なく、同じ機能のほとんどを可能にする別のスタイルのアプローチです (したがって、列挙型を定義しない場合でも使用できます)。また、実行時の範囲チェックも可能です。

私のアプローチの欠点は、列挙型とヘルパー クラスの間の結合をプログラムで強制しないため、それらを並行して更新する必要があることです。それは私にとってはうまくいきますが、YMMV.

于 2008-10-20T07:31:13.030 に答える
0

使用してくださいboost::variant

上記のアイデアをたくさん試してみて、それらが欠けていることを発見した後、私はこの単純なアプローチにたどり着きました:

#include <iostream>
#include <boost/variant.hpp>

struct A_t {};
static const A_t A = A_t();
template <typename T>
bool isA(const T & x) { if(boost::get<A_t>(&x)) return true; return false; }

struct B_t {};
static const B_t B = B_t();
template <typename T>
bool isB(const T & x) { if(boost::get<B_t>(&x)) return true; return false; }

struct C_t {};
static const C_t C = C_t();
template <typename T>
bool isC(const T & x) { if(boost::get<C_t>(&x)) return true; return false; }

typedef boost::variant<A_t, B_t> AB;
typedef boost::variant<B_t, C_t> BC;

void ab(const AB & e)
{
  if(isA(e))
    std::cerr << "A!" << std::endl;
  if(isB(e))
    std::cerr << "B!" << std::endl;
  // ERROR:
  // if(isC(e))
  //   std::cerr << "C!" << std::endl;

  // ERROR:
  // if(e == 0)
  //   std::cerr << "B!" << std::endl;
}

void bc(const BC & e)
{
  // ERROR:
  // if(isA(e))
  //   std::cerr << "A!" << std::endl;

  if(isB(e))
    std::cerr << "B!" << std::endl;
  if(isC(e))
    std::cerr << "C!" << std::endl;
}

int main() {
  AB a;
  a = A;
  AB b;
  b = B;
  ab(a);
  ab(b);
  ab(A);
  ab(B);
  // ab(C); // ERROR
  // bc(A); // ERROR
  bc(B);
  bc(C);
}

ボイラープレートを生成するためのマクロを考え出すことができるでしょう。(もしあれば教えてください。)

他のアプローチとは異なり、これは実際にはタイプ セーフであり、古い C++ で動作します。たとえば、 のようなクールな型を作成boost::variant<int, A_t, B_t, boost::none>して、ほとんど Haskell98 レベルのタイプ セーフである A、B、整数、または何もない値を表すこともできます。

注意すべき欠点:

  • 少なくとも古いブーストでは -- 私はブースト 1.33 のシステムを使用しています -- バリエーション内のアイテムは 20 に制限されています。ただし、回避策があります
  • コンパイル時間に影響する
  • 非常識なエラー メッセージ -- しかし、それは C++ です。

アップデート

ここでは、便宜上、typesafe-enum の「ライブラリ」を示します。このヘッダーを貼り付けます:

#ifndef _TYPESAFE_ENUMS_H
#define _TYPESAFE_ENUMS_H
#include <string>
#include <boost/variant.hpp>

#define ITEM(NAME, VAL) \
struct NAME##_t { \
  std::string toStr() const { return std::string( #NAME ); } \
  int toInt() const { return VAL; } \
}; \
static const NAME##_t NAME = NAME##_t(); \
template <typename T> \
bool is##NAME(const T & x) { if(boost::get<NAME##_t>(&x)) return true; return false; } \


class toStr_visitor: public boost::static_visitor<std::string> {
public:
  template<typename T>
  std::string operator()(const T & a) const {
    return a.toStr();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
std::string toStr(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toStr_visitor(), a);
}

class toInt_visitor: public boost::static_visitor<int> {
public:
  template<typename T>
  int operator()(const T & a) const {
    return a.toInt();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
int toInt(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toInt_visitor(), a);
}

#define ENUM(...) \
typedef boost::variant<__VA_ARGS__>
#endif

そして、次のように使用します。

ITEM(A, 0);
ITEM(B, 1);
ITEM(C, 2);

ENUM(A_t, B_t) AB;
ENUM(B_t, C_t) BC;

いくつかの魔法を破壊するマクロA_tの代わりに言わなければならないことに注意してください。しかたがない。また、関数と、文字列と int への単純な変換という OP の要件を満たす関数があることに注意してください。私が理解できない要件は、アイテムを反復処理する方法です。このような書き方をご存知でしたら教えてください。AENUMtoStrtoInt

于 2016-10-06T19:49:02.950 に答える
0

現在、 https: //bitbucket.org/chopsii/typesafe-enums で独自のタイプセーフ列挙型ライブラリを作成しています。

私はこれまでで最も経験豊富な C++ 開発者ではありませんが、BOOST ボールト列挙型の欠点のためにこれを書いています。

自由にチェックして自分で使用してください。ただし、いくつかの (できればマイナーな) 使いやすさの問題があり、おそらくクロスプラットフォームではまったくありません。

よろしければご寄稿ください。これは私の最初のオープンソースの取り組みです。

于 2015-01-16T08:51:14.537 に答える
-2

この投稿が遅すぎるかどうかはわかりませんが、5 番目のポイント (列挙子を反復処理する機能) 以外のすべてを満たす記事が GameDev.net にあります: http://www.gamedev.net/reference/snippets/features/cppstringizing/

この記事で説明されている方法を使用すると、コードを変更せずに、既存の列挙型の文字列変換をサポートできます。ただし、新しい列挙型のサポートのみが必要な場合は、Boost.Enum (上記) を使用します。

于 2009-06-07T23:52:15.467 に答える