web-dev-qa-db-ja.com

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

多くの場合、いくつかの列挙型を一緒に必要とします。時々、名前の衝突があります。これに対する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: //...
        //...
    }
  }
96
xtofl

元のC++ 03回答:

benefitnamespaceから(class経由で)using宣言を使用できることです君が望む時に。

namespaceを使用した場合の問題は、名前空間をコードの他の場所で展開できることです。大規模なプロジェクトでは、2つの異なる列挙型が両方ともeFeelingsと呼ばれるとは思わないという保証はありません。

見た目がシンプルなコードでは、おそらくコンテンツを公開したいので、structを使用します。

これらのプラクティスのいずれかを実行している場合、あなたは曲線の先を行き、おそらくこれをさらに詳しく調べる必要はありません。

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

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

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

66
Drew Dormann

参考までにC++ 0xには、あなたが言及したような場合のための新しい構文があります( C++ 0x wikiページ を参照)

enum class eColors { ... };
enum class eFeelings { ... };
20
jmihalicza

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

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

structはpublic:構文糖衣を避け、typedefは他のワーカークラス内でこれらの列挙型の変数を実際に宣言できます。

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

    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: ... ;
    }
}
11
Mark Lakata

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

上記の例から:

using namespace Colors;

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

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

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

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

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

個人的には、私はsingのファンではないので、完全修飾名を好むので、名前空間のプラスとしては本当に見えません。ただし、これはおそらくプロジェクトで行う最も重要な決定ではありません!

7
Richard Corden

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

_#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. 選択肢の文字列表現を返すなど、さらにメソッドを追加します。

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

7
Michał Górny

列挙型はスコープを囲むスコープにスコープされるため、グローバル名前空間の汚染を防ぎ、名前の衝突を防ぐために、おそらくsomethingで囲むのが最善です。 namespaceは保持の袋のように感じるので、classは堅牢なオブジェクトのように感じるため、名前空間をクラスよりも好みます(structclassの議論を参照)。名前空間の利点は、後で拡張できることです。変更できないサードパーティのコードを扱う場合に便利です。

もちろん、C++ 0xで列挙型クラスを取得する場合、これはまったく意味がありません。

5

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

リチャード・コーデンが示したように、クラスの利点は、c ++の意味での型であり、テンプレートで使用できることです。

基本的な機能を提供するすべてのテンプレートに特化した、ニーズに合わせた特別なtoolbox :: Enumクラスがあります(主に、I/Oを読みやすくするために、enum値を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ビットが必要)で列挙型に適合すること、およびこの表現に適合するすべてが有効な値と見なされることです。

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

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

3
Matthieu M.