2012年8月14日火曜日

継承 その2 (Inheritance 2)

継承 (Inheritance)オーバライド (override)ではサブクラスのインスタンス、関数テーブル構造体の中でスーパクラスのメンバを再定義していた。 今回はこれのうちインスタンスの構造体について、スーパクラスのメンバを記述しないで済む方法を考察する。 なお、関数テーブルもいろいろ試みてみたが、まだ良い方法が見つかっていないため、当面は現状の方式でいこうと思う。

なお、サブクラスの例としては IntegerVariableクラスは不適切である(サブクラスがスーパクラスの privateメンバを直接アクセスしていた)ため、LoggableIntegerクラスを用いる。

バイト列による置き換え (substitution by the byte array)

問題としているのはつぎの箇所である。 (LoggableInteger.cの一部)
struct LoggableInteger {
 interface_LoggableInteger interface_methods;

 /* Integer class */
 int value;

 /* LoggaableInteger class */
 int counter;
};

これの int valueの部分をサイズを保ったまま、抽象化してしまうために、つぎのようにしてみる。
struct LoggableInteger {
        interface_LoggableInteger interface_methods;
        char padding[sizeof_Integer];

        int counter;
};

sizeof_Integerは Integer.hで defineされている。これを char配列のサイズとすることでスーパクラスのインスタンスサイズを確保することができる。


サイズの定数 (constant number of the size)

問題は、sizeof_Integerをどのように求めるかである。
Integerクラスのインスタンス構造体のサイズを求めるには、
sizeof(struct Integer) 
とすれば良いのだが、隠蔽するために struct Integer構造体は Integer.hではなく、Integer.cに記述している。
したがって、LoggableIntegerクラスが Integer.hをインクルードしても、sizeofでサイズを求めることはできない。 そこで苦肉の策として、定数を直接ヘッダファイル Integer.hに記述している。
#define sizeof_Integer  4

そうすると、Integerクラスのインスタンス変数を変更すると、この値を忘れずに修正しなければならない。しかも、処理系によってメンバが整列されることがあるため、計算は単純ではない。
そこで、つきのような引数付きマクロを使って実行時に自己チェックするようにしてみる。
#define interface_sizeof(CLASS) \
        if (sizeof_##CLASS != sizeof(struct CLASS) - sizeof(void*)) { \
                printf("sizeof struct %s expected %d but %d\n", \
                        #CLASS, \
                        sizeof(struct CLASS) - sizeof(void*), \
                        sizeof_##CLASS); \
                abort(); \
        }


このマクロをつぎのように、クラスの初期化関数の中で呼び出す。
void static_Integer(interface_Integer interface_methods) {
        /* ToDo: lock interface_methods */
        if (interface_methods->finalize == NULL) {
                interface_sizeof(Integer);

                interface_methods->finalize = finalize;
                interface_methods->getValue = getValue;
        }
}


sizeof_Integerの値は最初は適当な値にしておけばよい。たとえば次のように 0としておく。
#define sizeof_Integer  0


これで以前に示したテストケースを実行すると、
$ ./IntegerTest
[CUnit] ./IntegerTest: testGetValue
sizeof struct Integer expected 4 but 0
signal: Abort(6)
[ABORT] testGetValue
[CUnit] ./IntegerTest: testNewAndFinalize
sizeof struct Integer expected 4 but 0
signal: Abort(6)
[ABORT] testNewAndFinalize
[ERROR] testGetValue in 803(ms)
[ERROR] testNewAndFinalize in 811(ms)
successes:    0 (0.0%)
failures:     0 (0.0%)
errors:       2 (100.0%)
total:        2 in 1614(ms)
$ 

このように、struct Integerのサイズは 4バイトであることが判明するため、defineの 0を 4に修正することができる。
一旦失敗して表示された値を用いる 2パス方式は、なんとも間抜けな気もするが、unit testを推進するという口実にしておこう。

なお、マクロはこのクラス以外でも使用したいために、適当なヘッダファイルに記述することにする。
また、このマクロは printf関数を使用しているため、どこかで stdio.hをインクルードする必要がある。
クラスによっては stdio.hをインクルードしたくない場合もあるため、この点は今後の課題とする。

あの少女は まるで恋だね。

0 件のコメント:

コメントを投稿