スポンサーサイト静的アサーション

Home > ----- / スポンサー広告 > This Entry 2005-09 / 実装技術 > This Entry [com : 2][Tb : 0]

--------

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

2005-09-21

 関数の引数の正当性は、可能な限り確認した方が、信頼性の高いプログラムになることはいうまでもありません。しかし、引数の確認に伴うオーバーヘッドも馬鹿にならないため、デバッグ時のみ確認を行うようにし、最終出荷時には確認を省略することがよくあります。そんなときに役立つのがおなじみのassertマクロ*1です。

さて、Cの場合には、引数といえば関数ぐらいしかない(厳密には関数形式マクロもある)わけですが、C++には、もうひとつ重要な引数を持つ言語機能があります。それはテンプレートです。関数の引数の正当性を確認すべきであるとするなら、当然、テンプレート引数の正当性も確認すべきです。

ところが、テンプレート引数はコンパイル時に作用するものですから、assertマクロで実行時に確認するのは純粋なオーバーヘッドになってしまいます。何とかコンパイル時に確認する方法があれば、実行時には全くコストを払わずに済ませることができます。

ここでは、定数式を与えて、その値が真の場合には何も起こらず、偽の場合はコンパイルエラーを故意に引き起こす「静的アサーション」を紹介します。C++によるジェネリックプログラミングでは、期待したセマンティックに反するコードに対して、積極的にコンパイルエラーを発生させるようなテクニックが多用されます。


 まず、Cでも可能な方法で、静的アサーションの実現方法を検討してみたいと思います。Cでも、コンパイル時に条件を判定したい場合がときどきありますので、CとC++に共通の方法があれば、何かと重宝するはずです。

#define STATIC_ASSERT(expr) \
    extern int static_assert_[(expr)?+1:-1]



上のコードは最も簡単な実装例です。STATIC_ASSERTマクロの引数に指定した定数式を評価した結果が真であれば、配列の要素数が+1になりますので何も起こりません。偽の場合には配列の要素数が-1になり、コンパイルエラーが発生します。
extern指定子を付けて外部宣言にしているため、STATIC_ASSERTをいくつ使用しても、必要なサイズは0です。

上記の実装例には若干の問題点があります。それは、関数の中で使用すると、コンパイラによってはstatic_assert_(ダミーの配列)が未使用である旨の警告が出る場合があります。その問題を対策したのが以下のものです。

#define STATIC_ASSERT(expr) \
    extern void static_assert_func_(int static_assert_ \
    [(expr)?+1:-1])



この方法では、判定用の配列を関数の仮引数として使用しているため、変数が未使用の旨の警告を回避することができます。
他にも、externの代わりにtypedefを使う方法も考えられますが、Cではtypedefを重複して使用することができないため、STATIC_ASSERTを複数使用したい場合には問題があります。


 上記のSTARIC_ASSERTマクロはCでもC++でも使える優れものですが、C++で使う場合には重大な問題があります。それは、クラス指定子の中で使えないということです。下の例を見てください。

template <class T>
class A
{
  STATIC_ASSERT(sizeof(T) > 8); // ← 常にエラー
};



つまり、クラス指定子ではexternが使えないため、条件式が真の場合でもコンパイルエラーになってしまいます。externを初めから外しておけば、とりあえずエラーにはなりませんが、いろいろな意味で安全性にかけるため、あまり得策ではありません。

そこで、条件式が偽のときに発生するエラーメッセージが多少なりともわかりやすいこともあり、C++では以下のようにテンプレートを使って静的アサーションを実現することの方が多いと思います。

template <bool> struct static_assert_
{
  enum { okay = 1 };
};

template <> struct static_assert_<false> {};

#define JOIN(a, b) JOIN_(a, b)
#define JOIN_(a, b) a ## b
#define STATIC_ASSERT(expr) \
    typedef int JOIN(diagnostics_, __LINE__) \
    [static_assert_<!!(expr)>::okay]



テンプレートでも、上のように特殊化を用いて、特定の場合の定義をあえて行わないことで、積極的にコンパイルエラーを発生させることができます。ジェネリックライブラリの開発においては、エラーが出ないようにするだけではなく、特定の状況では確実にエラーを発生させるためのテクニックも重要になるわけです。


*1 assertマクロはフリースタンディング環境ではサポートされるとは限りませんが、比較的簡単に同等のものを自作することができます。

Comment

fujii : 2009-06-27(Sat) 23:41 URL edit
性的アサーション!?
たかぎ : 2009-06-27(Sat) 23:45 URL edit
fujiさん、ご指摘ありがとうございます。
いつも変換しようとすると、先に「性的」が出てくるので、いつかやるだろうと思っていました。
Post a Comment









管理者にだけ表示を許可

Trackback

http://cppemb.blog17.fc2.com/tb.php/27-c00a4c1f

C++と組み込み環境 | Page Top▲

New >>
非局所オブジェクトの初期化
<< old
派生できないクラス
ブログ内検索
RSSフィード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。