列挙型の名前空間-ベストプラクティス


102

多くの場合、いくつかの列挙型を一緒に必要とします。時々、名前の衝突があります。これに対する2つの解決策が思い浮かびます:名前空間を使用するか、「より大きな」列挙型要素名を使用します。それでも、名前空間ソリューションには、2つの可能な実装があります。ネストされた列挙型を持つダミークラス、または完全な名前空間です。

3つのアプローチすべての長所と短所を探しています。

例:

// oft seen hand-crafted name clash solution
enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd };
enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd };
void setPenColor( const eColors c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cColorBlue: //...
        //...
    }
 }


// (ab)using a class as a namespace
class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
 }


 // a real namespace?
 namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
 namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
 void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
  }

19
まず、Color :: Red、Feeling:Angryなどを使用します
abatishchev 2009年

よい質問です。名前空間メソッドを使用しました...;)
MiniScalope

18
すべての接頭辞「c」は読みやすさを損ないます。
ユーザー、

8
@ユーザー:理由、ハンガリー語のディスカッションを開いていただきありがとうございます:)
xtofl

5
のように列挙enum e {...}型に名前を付ける必要はないことに注意してください。列挙型は匿名にすることができます。つまりenum {...}、名前空間またはクラスにラップされている場合、これはより意味があります。
Kralyk

回答:


73

元のC ++ 03回答:

(を超える)の利点は、namespaceclassusingお好きな時に宣言を。

使用に関する問題namespaceは、名前空間をコードの他の場所に展開できることです。大規模なプロジェクトでは、2つの異なる列挙型が両方ともそれらが呼び出されているとは思わないことは保証されませんeFeelings

より単純に見えるコードのために、私は struct、コンテンツを公開したいと思うのでします。

これらのプラクティスのいずれかを実行している場合、あなたは時代遅れであり、おそらくこれをさらに精査する必要はありません。

新しいC ++ 11のアドバイス:

C ++ 11以降を使用している場合、 enum class、enumの名前内のenum値が暗黙的にスコープされます。

を使用enum classすると、暗黙的な変換や整数型との比較が失われますが、実際には、あいまいなコードやバグのあるコードを発見するのに役立ちます。


4
struct-ideaに同意します。そしてお世辞に感謝:)
xtofl 2009年

3
+1 C ++ 11の「列挙型クラス」構文を思い出せませんでした。その機能がなければ、列挙型は不完全です。
Grault、2013

「列挙型クラス」の暗黙のスコープに「使用」を使用するオプションです。たとえば、「using Color :: e;」を追加します コード化して 'cRed'の使用を許可し、これがColor :: e :: cRedであることを確認するには
JVApen 2015年


11

上記の回答を次のようなものにハイブリッド化しました:(編集:これはC ++ 11以前の場合にのみ役立ちます。C++ 11を使用している場合は、を使用してくださいenum class

これらの列挙型はワーカークラス間で共有され、列挙型をワーカークラス自体に配置しても意味がないため、すべてのプロジェクト列挙型を含む大きなヘッダーファイルが1つあります。

struct公共回避:シンタックスシュガーを、そしてtypedefあなたが実際に他のワーカークラス内でこれらの列挙型の変数を宣言することができます。

名前空間を使用してもまったく役に立たないと思います。多分これは私がC#プログラマーであり、値を参照するときに列挙型の名前を使用する必要があるためです。

    struct KeySource {
        typedef enum { 
            None, 
            Efuse, 
            Bbram
        } Type;
    };

    struct Checksum {
        typedef enum {
            None =0,
            MD5 = 1,
            SHA1 = 2,
            SHA2 = 3
        } Type;
    };

    struct Encryption {
        typedef enum {
            Undetermined,
            None,
            AES
        } Type;
    };

    struct File {
        typedef enum {
            Unknown = 0,
            MCS,
            MEM,
            BIN,
            HEX
        } Type;
    };

...

class Worker {
    File::Type fileType;
    void DoIt() {
       switch(fileType) {
       case File::MCS: ... ;
       case File::MEM: ... ;
       case File::HEX: ... ;
    }
}

9

このためにクラスを使用することは絶対に避けます。代わりに名前空間を使用してください。問題は、名前空間を使用するか、列挙値に一意のIDを使用するかです。個人的には、名前空間を使用して、IDを短くして、説明がわかりやすくなるようにします。次に、アプリケーションコードは「using namespace」ディレクティブを使用して、すべてを読みやすくすることができます。

上記の例から:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}

クラスよりも名前空間を好む理由についてのヒントを教えてください。
xtofl 2009年

@xtofl: 'using class Colors`は記述できません
MSalters

2
@MSalters:Colors someColor = Red;名前空間は型を構成しないため、も記述できません。Colors::e someColor = Red;代わりに書く必要がありますが、これは直感に反しています。
SasQ

@SasQ ステートメントで使用したい場合Colors::e someColorでも使用する必要はありませんか?匿名を使用する場合、スイッチはを評価できません。struct/classswitchenumstruct
マクベスのエニグマ2013年

1
申し訳ありませんが、const e c私には読みにくいようです:-)しないでください。ただし、名前空間の使用は問題ありません。
dhaumann 2016

7

クラスまたは名前空間の使用の違いは、名前空間のようにクラスを再度開くことができないことです。これにより、名前空間が将来悪用される可能性を回避できますが、列挙のセットに追加できないという問題もあります。

クラスを使用することで考えられる利点は、テンプレートタイプの引数として使用できることです。これは名前空間には当てはまりません。

class Colors {
public:
  enum TYPE {
    Red,
    Green,
    Blue
  };
};

template <typename T> void foo (T t) {
  typedef typename T::TYPE EnumType;
  // ...
}

個人的には、私はを使用するのが好きではないので、完全修飾名を好むので、名前空間のプラスとは言えません。ただし、これはおそらく、プロジェクトで行う最も重要な決定ではありません。


クラスを再開しないことも潜在的な欠点です。色のリストも有限ではありません。
MSalters 2009年

1
クラスを復活させないことは潜在的な利点だと思います。より多くの色が必要な場合は、クラスをより多くの色で再コンパイルします。私がこれを行うことができない場合(たとえば、コードを持っていない場合)、どのような場合でも触れたくありません。
Thomas Eding、2011

@MSalters:クラスを再開する可能性は不利なだけでなく、安全ツールでもありません。クラスを再度開いて列挙にいくつかの値を追加することが可能な場合、すでにその列挙に依存し、古​​い値のセットのみを知っている他のライブラリコードを壊す可能性があるためです。次に、それらの新しい値を喜んで受け入れますが、実行時にそれらの処理方法がわからないために中断します。オープンクローズの原則を覚えておいてください。クラスは変更のためにクローズする必要がありますが、拡張のためオープンにする必要があります。拡張とは、既存のコードに追加するのではなく、新しいコードでそれをラップすること(派生など)を意味します。
SasQ

したがって、列挙型を拡張する場合は、最初の型から派生した新しい型にする必要があります(C ++で簡単に可能だった場合に限ります; /)。その後、それらの新しい値を理解する新しいコードで安全に使用できますが、古いコードでは古い値だけが(それらを下に変換して)受け入れられます。それらは、これらの新しい値からの1つを間違ったタイプ(拡張されたもの)として受け入れるべきではありません。彼らが理解する古い値だけが正しい(ベース)タイプであると受け入れられます(誤って新しいタイプでもあるため、新しいコードでも受け入れられる可能性があります)。
SasQ

7

クラスを使用する利点は、その上に本格的なクラスを構築できることです。

#include <cassert>

class Color
{
public:
    typedef enum
    {
        Red,
        Blue,
        Green,
        Yellow
    } enum_type;

private:
    enum_type _val;

public:
    Color(enum_type val = Blue)
        : _val(val)
    {
        assert(val <= Yellow);
    }

    operator enum_type() const
    {
        return _val;
    }
};

void SetPenColor(const Color c)
{
    switch (c)
    {
        case Color::Red:
            // ...
            break;
    }
}

上記の例が示すように、クラスを使用すると、次のことができます。

  1. (残念ながら、コンパイル時ではなく)C ++が無効な値からのキャストを許可しないようにします。
  2. 新しく作成された列挙型に(ゼロ以外の)デフォルトを設定し、
  3. 選択肢の文字列表現を返すようなメソッドを追加します。

operator enum_type()C ++がクラスを基礎となる列挙型に変換する方法を認識できるように宣言する必要があることに注意してください。そうしないと、型をswitchステートメントに渡すことができません。


何とかここに示されているものに関連し、この解決策はある?:en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum私は、私はこのパターンを毎回書き換える必要がないことをテンプレートにする方法について考えています使用する必要があります。
SasQ

@SasQ:似ているようです。それはおそらく同じ考えです。ただし、そこに多くの「一般的な」メソッドを追加しない限り、テンプレートが有益かどうかはわかりません。
のMichałGórny

1.真実ではない。enstが有効であることをconst_exprを介して、またはintのプライベートコンストラクターを介してコンパイル時にチェックできます。
xryl669 2014年

5

列挙型は、その外側のスコープにスコープされているので、それらをラップするために、おそらく最善です何かグローバルな名前空間を汚染回避するために、ヘルプ回避の名前の衝突に。私はクラスよりも名前空間を好むのは、単にnamespace保持の袋のclassように感じるのに対して、堅牢なオブジェクトのように感じるからです(struct対vs. class議論)。ネームスペースの考えられる利点は、後で拡張できることです-変更できないサードパーティのコードを扱っている場合に役立ちます。

もちろん、C ++ 0xを使用して列挙型クラスを取得する場合、これはすべて問題です。


列挙型クラス...それを調べる必要があります!
xtofl 2009年

3

また、列挙型をクラスでラップする傾向があります。

Richard Cordenが示唆したように、クラスの利点は、それがC ++の意味での型であり、テンプレートで使用できることです。

基本的な機能を提供するすべてのテンプレートに特化した、ニーズに合わせた特別なtoolbox :: Enumクラスがあります(主に、I / Oが読みやすくなるように列挙値をstd :: stringにマッピングします)。

私の小さなテンプレートには、許可された値を実際にチェックするという追加の利点もあります。コンパイラーは、値が実際に列挙型にあるかどうかをチェックするのが難しいです。

typedef enum { False: 0, True: 2 } boolean;
   // The classic enum you don't want to see around your code ;)

int main(int argc, char* argv[])
{
  boolean x = static_cast<boolean>(1);
  return (x == False || x == True) ? 0 : 1;
} // main

意味のない(そして期待しない)列挙値が残っているため、コンパイラーがこれをキャッチしないことにいつも悩まされていました。

同様に:

typedef enum { Zero: 0, One: 1, Two: 2 } example;

int main(int argc, char* argv[])
{
  example y = static_cast<example>(3);
  return (y == Zero || y == One || y == Two) ? 0 : 1;
} // main

もう一度mainはエラーを返します。

問題は、コンパイラーが列挙型を利用可能な最小の表現(ここでは2ビットが必要)に適合させることと、この表現に適合するすべてが有効な値と見なされることです。

列挙型に値を追加するたびにすべてのスイッチを変更する必要がないように、スイッチの代わりに可能な値のループが必要になる場合があるという問題もあります。

全体として、私の小さなヘルパーは列挙型の処理を本当に容易にします(もちろん、オーバーヘッドが追加されます)。それが可能なのは、各列挙型を独自の構造体にネストしているためです:)


4
面白い。Enumクラスの定義を共有してもよろしいですか?
momeara
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.