実行時のクラス判定

変なマクロ

こんなマクロを好んで使う人がいます。

#define CheckPointer(p,ret) { if ((p) == NULL) return (ret); }

これは、典型的には函数の引数をチェックして、間違って NULL が渡された ときにエラーとして却下しようというものです。

        int Hoge::Munya(char * str)
        {
                CheckPointer(str, ERR_NULL_STRING);
                /* 処理の実体 */
        }

というように使えば、引数 str が NULL であったとき、エラーコード ERR_NULL_STRING を返し、以降の処理の実体は実行しないようになります。

何も知らない人がこのコードを見たら、CheckPointer() は函数だと思うことでしょう。 昔、Cにインライン函数の機能がなかったころは、マクロで代用したくらいですから、 まあこれはねらい通りといったところ。

そして、マクロが展開されたときに変な風に構文解析されてしまわないよう、 余分な括弧を入れるなど気を配る必要がありました。 括弧が足りなかったために起こった悲劇についてはあちこちで紹介されているので ここでは述べませんが、 変なバグに悩まされたくなかったらインライン函数にしておくのが常套手段です。

でもよく考えてみると CheckPointer() は、return が入っているため インライン函数に置き換えることができないマクロなんですね。 函数のつもりでこのコードを読むと、「処理の実体」が実行されない 場合があるとは、直感的に把握できないわけです。 こういう使い方は、やめて欲しいなあ、と私なんかは思うんですけどね。


実行時のクラス判定

それはさておき、実際には単に NULL でないというだけでなく、 本当に目的とするクラスに属するオブジェクトかどうかも確かめないと 不安になることがあります。例えば、Bera がクラス名であるとして、

        int Hoge::Munya(Bera * bera)
        {
                ...
        }

となっているとします。

        Hoge    hoge;
        Anya *  anya = new Anya();
        hoge.Munya(anya);

という呼び出しをすれば、Hoge::Munya() にクラスAnyaのオブジェクトを渡している 訳ですからコンパイル時にエラーになりますが、

        hoge.Munya((Bera *) anya);

というようにキャストをすればコンパイルはできてしまいます。

これは要するに、実行時に自分がどのクラスに属するのか判定する、という機能に 帰着します。 上記のような引数チェックのためばかりでなく、例えば Bera の派生クラス Bera1とBera2があったとして、

        int Hoge::Munya(Bera * bera)
        {
                if (bera instanceof Bera1) {
                        /* Bera1の処理 */
                }
                else if (bera instanceof Bera2) {
                        /* Bera2の処理 */
                }
        }

というようにクラスによって処理を振り分けたいこともあります。 まあこの場合ならば

        int Hoge::Munya(Bera1 * bera)
        int Hoge::Munya(Bera2 * bera)

というように、名前は同じで引数型の違う2つの函数を用意する手もありますが、 いつもこう簡単にいくとは限りません。

上でさりげなくinstanceofというキーワードを使いましたが、 これはJavaで使われているもの。 C++には相当する機能が無いので、無理に処理の流れをねじまげてしまうこともあります。


解決方法の実例

例えばMFC(Microsoft Foundation Class)というクラス・ライブラリーでは、

        bera->IsKindOf(RUNTIME_CLASS(Bera))

という書き方ができます。これは、bera がクラスBeraのインスタンスであるとき、 0以外の値になります。

さっそく試してみようという人もいるかも知れないので念のため。 クラスBeraは、CObjectの派生クラスとしておかなければなりません。 はまりやすいのは、CPointやCStringのようにCObjectから派生していないクラスで これをやってしまうこと。

この機能は、クラス名や親クラスに関する情報を収めた CRuntimeClass型構造体を比較することによって、実現しています。

とはいえ、移植性のない方法ではありますから、 私は控えめに、デバッグ時の型チェック(アサーション)にのみ使うようにしています。 この場合は専用のマクロが定義されていて、

        ASSERT_KINDOF(Bera, bera);

と短く書くこともできます。


(参考)

新しい機能で、 typeid演算子を使ってtype_infoオブジェクトを 取得し、クラス名を調べたりすることもできます。 でもASSERT_KINDOFほど手軽に使うことができないのが難点。