web-dev-qa-db-ja.com

#define、enum、またはconstを使用する必要がありますか?

私が取り組んでいるC++プロジェクトでは、4つの値を持つことができるflagのような値があります。これらの4つのフラグは組み合わせることができます。フラグはデータベース内のレコードを説明し、次のいずれかです。

  • 新記録
  • 削除されたレコード
  • 変更されたレコード
  • 既存の記録

さて、各レコードに対してこの属性を保持したいので、enumを使用できます。

enum { xNew, xDeleted, xModified, xExisting }

ただし、コード内の他の場所では、ユーザーに表示するレコードを選択する必要があるため、次のように単一のパラメーターとして渡すことができるようにします。

showRecords(xNew | xDeleted);

だから、私は3つの可能なappoachesがあるようです:

#define X_NEW      0x01
#define X_DELETED  0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08

または

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

または

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

スペース要件は重要ですが(バイト対整数)、重要ではありません。定義では型の安全性が失われ、enumでは一部のスペース(整数)が失われるため、ビット単位の演算を行うにはおそらくキャストする必要があります。 constを使用すると、ランダムなuint8が誤って入力される可能性があるため、型の安全性も失われます。

他のクリーンな方法はありますか?

そうでない場合、何を使用しますか?その理由は何ですか?

追伸残りのコードは#definesのないかなりきれいな最新のC++であり、いくつかのスペースで名前空間とテンプレートを使用しているため、これらも問題になりません。

122
Milan Babuškov

戦略を組み合わせて、単一のアプローチの欠点を減らします。私は組み込みシステムで働いているので、次のソリューションは、整数演算子とビット演算子が高速で、メモリが少なく、フラッシュ使用量が少ないという事実に基づいています。

ネームスペースに列挙を配置して、定数がグローバルネームスペースを汚染しないようにします。

namespace RecordType {

列挙型は、チェックイン型のコンパイル時を宣言および定義します。常にコンパイル時の型チェックを使用して、引数と変数に正しい型が指定されていることを確認してください。 C++ではtypedefは不要です。

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

無効な状態の別のメンバーを作成します。これはエラーコードとして役立ちます。たとえば、状態を返したいが、I/O操作が失敗した場合。デバッグにも役立ちます。初期化リストとデストラクタで使用して、変数の値を使用する必要があるかどうかを確認します。

xInvalid = 16 };

このタイプには2つの目的があると考えてください。レコードの現在の状態を追跡し、特定の状態のレコードを選択するためのマスクを作成します。インライン関数を作成して、型の値が目的に合っているかどうかをテストします。状態マーカーと状態マスクとして。 typedefは単なるintであり、0xDEADBEEFなどの値が初期化されていない変数または誤った変数を介して変数にある可能性があるため、これはバグをキャッチします。

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

タイプを頻繁に使用する場合は、usingディレクティブを追加します。

using RecordType ::TRecordType ;

値チェック関数は、使用時にすぐに不良値をトラップするアサートで役立ちます。実行中にバグを迅速にキャッチすればするほど、バグが少なくなります。

すべてをまとめるための例をいくつか示します。

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

正しい値の安全性を確保する唯一の方法は、オペレーターのオーバーロードを持つ専用のクラスを使用することです。これは、他の読者の演習として残されています。

86
mat_geek

定義を忘れる

彼らはあなたのコードを汚染します。

ビットフィールド?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

これを使用しないでください。 4つの整数を節約するよりも速度に関心があります。ビットフィールドの使用は、実際には他のタイプへのアクセスよりも遅くなります。

ただし、構造体のビットメンバーには実用上の欠点があります。まず、メモリ内のビットの順序はコンパイラによって異なります。加えて、 多くの一般的なコンパイラは、ビットメンバーの読み取りおよび書き込み用に非効率的なコードを生成します、および潜在的に深刻な スレッドの安全性の問題 ほとんどのマシンはメモリ内の任意のビットセットを操作できないが、代わりに単語全体をロードおよび保存する必要があるため、ビットフィールド(特にマルチプロセッサシステム)に関連します。たとえば、ミューテックスを使用しているにもかかわらず、以下はスレッドセーフではありません

ソース: http://en.wikipedia.org/wiki/Bit_field

そして、notビットフィールドを使用しない理由がさらに必要な場合、おそらく Raymond Chen は彼の The Old New Thing Post:http://blogs.msdn。でのブール値のコレクションのビットフィールドの費用便益分析。 com/oldnewthing/archive/2008/11/26/9143050.aspx

const int?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

それらを名前空間に入れるのはクールです。 CPPまたはヘッダーファイルで宣言されている場合、それらの値はインライン化されます。これらの値でスイッチを使用できますが、カップリングがわずかに増加します。

ああ、はい:静的キーワードを削除します。 staticはC++では使用時に非推奨になり、uint8がビルドインタイプの場合、同じモジュールの複数のソースに含まれるヘッダーでこれを宣言するためにこれは必要ありません。最終的に、コードは次のようになります。

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

このアプローチの問題は、コードが定数の値を知っていることであり、これにより結合がわずかに増加します。

列挙型

Const intと同じですが、やや強い型付けが行われます。

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

ただし、まだグローバル名前空間を汚染しています。ところで...typedefを削除します。 C++で作業しています。列挙型および構造体のtypedefは、他の何よりもコードを汚染しています。

結果はちょっとです:

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

ご覧のとおり、enumはグローバル名前空間を汚染しています。この列挙型を名前空間に配置すると、次のようになります。

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

extern const int?

結合を減らしたい場合(つまり、定数の値を非表示にできるため、完全な再コンパイルを必要とせずに必要に応じて変更できる場合)、intをヘッダーでexternとして、CPPファイルで定数として宣言できます、次の例のように:

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

そして:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

ただし、これらの定数でスイッチを使用することはできません。最後に、毒を選んでください... :-p

54
paercebal

Std :: bitsetを除外しましたか?フラグのセットが目的です。行う

typedef std::bitset<4> RecordType;

それから

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

ビットセットには多数の演算子オーバーロードがあるため、次のことができます。

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

またはそれに非常に類似した何か-私はこれをテストしていないので、どんな修正でも感謝します。インデックスでビットを参照することもできますが、通常は定数のセットを1つだけ定義するのが最善であり、おそらくRecordType定数の方が便利です。

あなたがビットセットを除外していると仮定して、私はenumに投票します。

私は列挙型をキャストすることは重大な不利益だとは思わない-OK実装。ただし、必要な場合(intからenum iircに移行する場合)にのみ実行する場合、これまでに見たことのある完全に正常なコードです。

Enumのスペースコストについても疑問です。 uint8の変数とパラメーターはおそらくintより少ないスタックを使用しないため、クラス内のストレージのみが重要です。構造体で複数のバイトをパックすると勝つ場合があります(その場合、uint8ストレージに列挙型を出し入れできます)が、通常、パディングはいずれにせよ利益を殺します。

そのため、enumには他の列挙型と比べて不利な点はなく、利点として、タイプセーフ(明示的にキャストせずにランダムな整数値を割り当てることはできません)およびすべてを参照するクリーンな方法が得られます。

ちなみに、私は列挙型に「= 2」も入れます。必ずしも必要ではありませんが、「最小限の驚きの原則」は、4つの定義すべてが同じに見えることを示唆しています。

30
Steve Jessop

Constとマクロ、enumsに関する記事を次に示します。

記号定数
列挙定数と定数オブジェクト

新しいコードのほとんどは最新のC++で書かれているため、特にマクロは避けるべきだと思います。

8
Abbas

可能であれば、マクロを使用しないでください。現代のC++に関しては、あまり賞賛されていません。

5
INS

列挙型は、「識別子への意味」とタイプセーフティを提供するため、より適切です。 「xDeleted」は「RecordType」であり、「レコードのタイプ」を表していることは明らかです(すごい!)。 Constsはそのためのコメントを必要とし、またコード内で上下する必要があります。

4
hayalci

定義で型安全性を失う

必ずしも...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

そして、列挙型を使用すると、スペースが失われます(整数)

必ずしもではありません-しかし、あなたはストレージのポイントで明示的にする必要があります...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

そして、おそらくビット単位の操作を行いたいときにキャストする必要があります。

オペレーターを作成して、その痛みを取り除くことができます。

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

Constでは、ランダムなuint8が誤って入力される可能性があるため、型の安全性も失われると思います。

これらのメカニズムのいずれでも同じことが起こります:通常、範囲と値のチェックは型安全性に直交します(ただし、ユーザー定義型(つまり、独自のクラス)はデータについて「不変式」を強制できます)。列挙型では、コンパイラが自由に大きな型を選択して値をホストし、初期化されていない、破損した、または単に設定されていない列挙型変数は、ビットパターンを予期しない数値として解釈する可能性があります-列挙識別子、それらの任意の組み合わせ、および0。

他のクリーンな方法はありますか? /そうでない場合は、何を使用しますか?その理由は何ですか?

最後に、ビットフィールドとカスタム演算子が写真にあれば、試行錯誤された信頼できるCスタイルのビット単位のOR列挙型はかなりうまく機能します。 mat_geekの答えのように、カスタム検証関数とアサーション;文字列、int、double値などの処理にしばしば適用されるテクニック。

これは「クリーナー」であると言えます。

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

私は無関心です:データビットはより密集しますが、コードは大幅に成長します...持っているオブジェクトの数に依存し、lamdbas-そのままで美しい-はビット単位のORよりもさらに厄介で難しくなります。

ところで/-スレッドセーフティの非常に弱いIMHOに関する議論-支配的な意思決定の推進力になるのではなく、背景の考慮事項として最もよく記憶されています。ビットフィールド間でミューテックスを共有することは、それらのパッキングに気付いていない場合でも、より可能性の高いプラクティスです(ミューテックスは比較的かさばるデータメンバーです-1つのオブジェクトのメンバーに複数のミューテックスを持つことを考慮するために、パフォーマンスを本当に心配する必要があり、注意深く見ますビットフィールドであることに気付くのに十分です)。サブワードサイズのタイプでも同じ問題が発生する可能性があります(例:uint8_t)。とにかく、より高い並行性を求めているなら、アトミックな比較と交換スタイルの操作を試すことができます。

4
Tony Delroy

列挙型を格納するために4バイトを使用しなければならない場合(C++にはあまり慣れていません-C#で基になる型を指定できることはわかっています)、それでも価値があります-列挙型を使用します。

今日、GBのメモリを搭載したサーバーの時代では、一般的にアプリケーションレベルで4バイトと1バイトのメモリのようなものは関係ありません。もちろん、特定の状況でメモリ使用量が非常に重要な場合(そして、C++に列挙をバックアップするためにバイトを使用させることができない場合)、「静的const」ルートを検討できます。

結局のところ、あなたは自問する必要があります。あなたのデータ構造のために3バイトのメモリ節約のために「静的const」を使用するメンテナンスヒットの価値がありますか?

留意すべきその他の点-II86、x86では、データ構造は4バイトに揃えられているため、「レコード」構造にバイト幅の要素がいくつかない限り、実際には問題になりません。パフォーマンス/スペースの保守性のトレードオフを行う前に、それがテストされていることを確認してください。

3
Jonathan Rupp

列挙構文とビットチェックの利便性を備えたクラスの型安全性が必要な場合は、 C++の安全なラベル を検討してください。私は著者と協力してきましたが、彼はかなり頭がいいです。

ただし、注意してください。最後に、このパッケージはテンプレートandマクロを使用します!

3
Don Wakefield

Qtを使用している場合は、 QFlags を探す必要があります。 QFlagsクラスは、列挙値のOR組み合わせを格納するタイプセーフな方法を提供します。

2
Thomas Koschel

実際にフラグ値を概念的な全体として渡す必要がありますか、それともフラグごとのコードがたくさんありますか?いずれにせよ、これを1ビットのビットフィールドのクラスまたは構造体として持つと、実際に明確になると思います。

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

次に、レコードクラスにstruct RecordFlagメンバー変数を含めることができ、関数はstruct RecordFlag型の引数を取ることができます。コンパイラはビットスペースをまとめてパックし、スペースを節約します。

2
wnoise

私はおそらく、値を一緒に組み合わせることができるこの種のことには列挙型を使用しないでしょう。より一般的には、列挙型は相互に排他的な状態です。

しかし、どちらの方法を使用しても、これらが一緒に結合できるビットの値であることをより明確にするには、代わりに実際の値に次の構文を使用します。

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

ここで左シフトを使用すると、各値が単一ビットであることを示すのに役立ちます。後で誰かが新しい値を追加して値9を割り当てるなど、何か間違ったことをする可能性は低くなります。

2
Michael G

[〜#〜] kiss [〜#〜]高凝集度および低結合度 に基づいて、これらの質問をします-

  • 誰が知る必要がありますか?私のクラス、私のライブラリ、他のクラス、他のライブラリ、サードパーティ
  • どのレベルの抽象化を提供する必要がありますか?消費者はビット操作を理解していますか。
  • VB/C#などからインターフェイスする必要がありますか?

素晴らしい本「 大規模C++ソフトウェアデザイン 」があります。これは、他のヘッダーファイル/インターフェイスの依存関係を回避できる場合は、外部で基本型を促進します。

2
titanae

私はむしろ行きたい

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

単純に〜だから:

  1. よりクリーンで、コードを読みやすく保守しやすくします。
  2. 論理的に定数をグループ化します。
  3. あなたの仕事isがこれらの3バイトを保存しない限り、プログラマの時間はより重要です。
0
Vivek

すべてをオーバーエンジニアリングするのが好きというわけではありませんが、これらのケースでは、この情報をカプセル化する(小さな)クラスを作成する価値がある場合があります。クラスRecordTypeを作成すると、次のような関数が含まれる場合があります。

void setDeleted();

void clearDeleted();

bool isDeleted();

など...(または任意の慣例に適合)

組み合わせを検証できます(たとえば、「新規」と「削除済み」の両方を同時に設定できない場合など、すべての組み合わせが有効ではない場合)。ビットマスクなどを使用しただけであれば、状態を設定するコードを検証する必要があり、クラスはそのロジックもカプセル化できます。

クラスはまた、各状態に意味のあるログ情報を添付する機能を提供する場合があります。現在の状態などの文字列表現を返す関数を追加できます(またはストリーミング演算子「<<」を使用します)。

ストレージについて心配する場合は、クラスに「char」データメンバーのみを持たせることができます。そのため、少量のストレージのみを使用します(仮想ではないと仮定します)。もちろん、ハードウェアなどによっては、アライメントの問題が発生する場合があります。

実際のビット値は、ヘッダーファイルではなくcppファイル内の匿名ネームスペースにある場合、残りの「世界」には見えない可能性があります。

Enum /#define/bitmaskなどを使用するコードに無効な組み合わせやロギングなどを処理するための「サポート」コードが多数あることがわかった場合は、クラスへのカプセル化を検討する価値があります。もちろん、ほとんどの場合、単純な問題は単純な解決策の方が優れています...

0
Michael