C / C ++では、プログラムのコンパイル段階で定数式をチェックできます。 これは、将来コードを変更する際の問題を回避する安価な方法です。
私はと働くことを検討します:
- 転送(enum)により、
- 配列(enumとの同期)、
- スイッチ構造
- また、異種データを含むクラスを操作します。
BOOST_STATIC_ASSERTおよびall-all-all
コンパイル時にコンパイラを壊す
方法は
たくさんあります 。 これらのうち、私は何よりもパフォーマンスが気に入っています。
#define ASSERT(cond) typedef int foo[(cond) ? 1 : -1]
ただし、プログラムでブーストを使用する場合、
BOOST_STATIC_ASSERTのようなものを発明する必要はありません。 サポートは、C ++ 11(static_assert)で
行われることも
約束しています 。
ツールを整理し、使用方法について説明します。
列挙型の要素の数を制御する
列挙は、意味によって関連付けられた定数のセットであり、通常はプログラムロジックの分岐点で使用されます。 通常、分岐点がいくつかあり、何かを簡単にスキップできます。
例:
enum TEncryptMode { EM_None = 0, EM_AES128, EM_AES256, <b>EM_ItemsCount</b> };
最後の要素はアルゴリズムではなく、最大セマンティック要素よりも1大きい補助定数です。
これで、このセットの定数が使用される場合はいつでも、チェックを追加するだけです。
ASSERT(EM_ItemsCount == 3);
将来新しい定数が追加されると、この時点でコードはコンパイルを停止します。 そのため、変更の作成者はコードのこのセクションを確認し、必要に応じて新しい定数を考慮する必要があります。
EM_ItemsCountの導入からのボーナスとして、関数パラメーターのランタイムチェックを挿入することが可能になります。
assert( 0 <= mode && mode < EM_ItemsCount );
そのような定数のないオプションと比較してください。
assert( 0 <= mode && mode <= EM_AES256 );
(EM_AES512を追加して、間違ったチェックを取得します)
配列と列挙
前のセクションからの検証の特殊なケース。
同じ暗号化アルゴリズムのパラメーターを持つ配列があるとします(この例は指から少し吸い上げられていますが、実際には同様のケースがあります)。
static const ParamStruct params[] = { { EM_None, 0, ... }, { EM_AES128, 128, ... }, { EM_AES256, 256, ... }, { -1, 0, ... } };
この構造をTEncryptModeと同期させる必要があります。
(なぜ配列の最後の要素が必要なのか、説明する必要はないと思います。)
配列の長さを計算するための補助マクロが必要です。
#define lengthof(x) (sizeof(x) / sizeof((x)[0]))
これで、チェックを書くことができます(params定義の直後が良い場合):
ASSERT( lengthof(params) == EM_ItemsCount + 1 );
upd: コメントでは、
skor habrayuzerはlengthofマクロのより安全なバージョンを提案しました。
スイッチ
ここですべてが明らかです(上記の例の後)。 スイッチ(モード)を追加する前に:
ASSERT(EM_ItemsCount == 3);
少しわかりにくいランタイムチェック:
ASSERT(EM_ItemsCount == 3); switch( mode ) { case ...: ... break; ... <b>default: assert( false );</b> }
ミスに対する防御のための追加の要塞。 アクションが同じ方法で処理される場合、1つのアクションに対して複数のケース条件をリストし、デフォルトを空のままにしておくことをお勧めします。
... case ET_AES128: case ET_AES256: ... break; ...
異種データを持つクラス
enum'ovから脱線して、そのようなクラスを見てみましょう。
class MyData { ... private: int a; double b; ... };
将来、誰かがint c変数を追加したいと思うかもしれません。 この時までにクラスは大きく複雑になりました。 変数cを書き込むポイントを見つける方法は?
このような半自動解法が提案されています-クラスにデータバージョン定数定数を設定します。
class MyData { static const int DataVersion = 0; ... };
これで、すべてのデータの整合性を追跡することが重要なすべての方法で、次のように記述できます。
ASSERT(DataVersion == 0);
クラスに新しいデータを追加するには、DataVersion定数を手動で増やす必要があります(ここでは、規律が必要です)。 ただし、コンパイラはチェックする必要のある場所にすぐに注意を払います。 これらの検証ポイントには次のものが含まれます。
- デザイナー
- 代入演算子(演算子=)
- 比較演算子(==、<など)、
- データの読み取り/書き込み(<<、>>を含む)、
- デストラクタ(些細でない場合)。
残りの検証場所は、内部ロジック(たとえば、ログへの出力)に依存します。
データをディスクに保存するときに同じ定数(DataVersion)を使用すると便利です(興味がある場合は、個別に記述できます)。
ベネフィット
結果は何ですか?
長所:
- コンパイル段階での自動整合性チェック(場合によっては、数時間、さらには数日間のデバッグを節約できます)。
- 実行時のオーバーヘッドがゼロ。
短所:
- 追加コード(比較的小さいとはいえ)。
- 自己規律の負荷(定数を修正するだけでなく、トリガーされたフォールを見るだけでよい)。
私にとって、利点はそれを上回りますが、あなたにとっては?
updコードの強調表示を追加しました。