1. ホーム
  2. c++

[解決済み] define、enum、constのどれを使うべきですか?

2022-08-03 12:26:11

質問

現在取り組んでいるC++のプロジェクトで フラグ という種類の値があり、これは4つの値を持つことができます。これらの4つのフラグは組み合わせることができます。フラグは、データベース内のレコードを記述し、することができます。

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

さて、各レコードについて、私はこの属性を保持したいので、私は列挙を使用することができます。

enum { xNew, xDeleted, xModified, xExisting }

しかし、コードの他の場所で、どのレコードをユーザーに見せるかを選択する必要があるので、次のように1つのパラメータとして渡せるようにしたいと思います。

showRecords(xNew | xDeleted);

というわけで、3つのアプローチが考えられるようです。

#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;
}

スペース要件は重要ですが(byteとint)、重要ではありません。定義があると型安全性が失われ、また enum ではスペース(整数)を失い、ビット演算をしたいときにはキャストしなければならないかもしれません。しかし const を使うと、型安全性も失います。 uint8 が間違って入ってしまう可能性があるからです。

他にもっときれいな方法はないのでしょうか?

ない場合、何を使いますか、またその理由は?

P.S. 残りのコードは、むしろクリーンなモダンC++で #define のない、むしろクリーンなモダンC++です。そして、私はわずかなスペースで名前空間とテンプレートを使用しているので、それらも問題外ではありません。

どのように解決するのですか?

1つのアプローチのデメリットを減らすために戦略を組み合わせます。私は組み込みシステムで仕事をしているので、以下の解決策は、整数およびビット演算子が高速で、メモリ使用量が少なく、フラッシュ使用量も少ないという事実に基づいています。

グローバルな名前空間を定数が汚染しないように、enum を名前空間に配置します。

namespace RecordType {

enumはコンパイル時に型チェックをしたものを宣言・定義しています。引数や変数に正しい型が与えられていることを確認するために、常にコンパイル時の型チェックを行います。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;
}

正しい値の安全性を保証する唯一の方法は、演算子のオーバーロードを持つ専用のクラスを使用することですが、それは別の読者のための練習として残されています。