2012年8月13日月曜日

オーバライド (override)

前回の 継承 (Inheritance)ができたので、これを応用すればスーパクラスで定義されている関数をサブクラス側で上書きする、オーバライドができる。


Integerクラスのサブクラス LoggableIntegerクラスには、スーパクラスの getValue関数をオーバライドする。この関数は Integerのそれど同様 int型の値を返すが、この関数を呼び出した回数を変数 counterに記録し、getAccessCount関数で読み出せるようにする。








ヘッダファイル (header file)

LoggableInteger.hはつぎの通り。
/* 13.Aug.2012 kei */

#ifndef _Included_LoggableInteger
#define _Included_LoggableInteger

typedef struct LoggableInteger* LoggableInteger;

typedef struct interface_LoggableInteger {

        void (*finalize)(LoggableInteger this);
        int (*getValue)(LoggableInteger this);

        int (*getAccessCount)(LoggableInteger this);
}* interface_LoggableInteger; 

extern void static_LoggableInteger(interface_LoggableInteger interface_methods);

extern LoggableInteger new_LoggableInteger(int value);

extern void LoggableInteger_init(LoggableInteger this, int value);

extern void LoggableInteger_finalize(LoggableInteger this);

extern int LoggableInteger_getValue(LoggableInteger this);

extern int LoggableInteger_getAccessCount(LoggableInteger this);

#endif /* _Included_LoggableInteger */

変数や関数が違う点以外は IntegerValueクラスのヘッダと同様。

ソースファイル (source file)

LoggableInteger.cはつぎの通り。
/* 13.Aug.2012 kei */

#include <LoggableInteger.h>
#include <Integer.h>

/****************************************************************
 private
****************************************************************/

#include <malloc.h>

struct LoggableInteger {
        interface_LoggableInteger interface_methods;

        /* Integer class */
        int value;

        /* LoggaableInteger class */
        int counter;
};

static struct interface_LoggableInteger interface_methods;
static struct interface_Integer super;

static int getValue(LoggableInteger this) {
        ++this->counter;
        return Integer_getValue((Integer)this);
}

static int getAccessCount(LoggableInteger this) {
        return this->counter;
}

/****************************************************************
 public
****************************************************************/

void static_LoggableInteger(interface_LoggableInteger interface_methods) {
        /* ToDo: lock interface_methods */
        if (interface_methods->finalize == NULL) {
                static_Integer(&super);
                static_Integer((interface_Integer)interface_methods);

                interface_methods->getValue = getValue;
                interface_methods->getAccessCount = getAccessCount;
        }
}

LoggableInteger new_LoggableInteger(int value) {
        static_LoggableInteger(&interface_methods);

        LoggableInteger this =
                (LoggableInteger)malloc(sizeof(struct LoggableInteger));

        if (this != NULL)
                LoggableInteger_init(this, value);

        return this;
}

void LoggableInteger_init(LoggableInteger this, int value) {
        Integer_init((Integer)this, value);
        this->interface_methods = &interface_methods;
}

void LoggableInteger_finalize(LoggableInteger this) {
        this->interface_methods->finalize(this);
}

int LoggableInteger_getValue(LoggableInteger this) {
        return this->interface_methods->getValue(this);
}

int LoggableInteger_getAccessCount(LoggableInteger this) {
        return this->interface_methods->getAccessCount(this);
}

これも IntegerVariableクラスとほぼ同じだが、getValue関数をオーバライドするため、LoggableIntegerクラスで改めて定義し、static_LoggableIntegerで getValue関数を設定している。
static_Integer関数を呼び出した時点で、interface_methodsには一旦スーパクラスの getValue関数が設定される。その後でサブクラスの関数を上書きしている。 概念だけでなく実際に上書きしているので、如何にも "override"の感じがする。

関数テーブルは interface_methodsのほかに Integerクラス用の superも定義している。
これは、Integerクラスの getValueを呼び出すために必要となる。

getValue関数の中では、 super.getValue((Integer)this); を呼び出すことにより、スーパクラスの取得関数を利用している。

動作確認 (unit test)

テストケース LoggableIntegerTest.cはつぎの通り。
/* 14.Aug.2012 kei */

#define import_lwd_unit_Assert
#define import_lwd_unit_CUnit

#include <lwd/unit/Assert.h>
#include <lwd/unit/CUnit.h>
#include <LoggableInteger.h>

/****************************************************************
 test cases
****************************************************************/

static LoggableInteger instance;

void setUp() {
        instance = new_LoggableInteger(813);
}

void tearDown() {
        LoggableInteger_finalize(instance);
}

void testNewAndFinalize() {
        /* setUp, tearDown */
}

void testGetValue() {
        int value = LoggableInteger_getValue(instance);
        Assert_equalsInt("A value must be 813.", 813, value);
}

void testGetAccessCount() {
        Assert_equalsInt("must be zero.",
                0, LoggableInteger_getAccessCount(instance));

        LoggableInteger_getValue(instance);

        Assert_equalsInt("must be 1.",
                1, LoggableInteger_getAccessCount(instance));
}

/****************************************************************
 main
****************************************************************/

int main(int argc, char** argv) {
        return CUnit_test(argc, argv);
}

テストのビルドと実行結果も残しておく。
$ make
cc -g -finstrument-functions -I. -I /export/home/kei/c/core/include -I /export/home/
kei/c/experimental/include -I /export/home/kei/c/io/include -I /export/home/kei/c/lang
/include -I /export/home/kei/c/message/include -I /export/home/kei/c/unit/include -I
 /export/home/kei/c/util/include -I /export/home/kei/c/x11/include -I ../../char/
include -I ../../class1/include  -c -o LoggableIntegerTest.o LoggableIntegerTest.c
$ ./LoggableIntegerTest
[CUnit] ./LoggableIntegerTest: testGetAccessCount
[CUnit] ./LoggableIntegerTest: testGetValue
[CUnit] ./LoggableIntegerTest: testNewAndFinalize
[SUCCEED] testGetAccessCount in 1(ms)
[SUCCEED] testGetValue in 2(ms)
[SUCCEED] testNewAndFinalize in 2(ms)
successes:    3 (100.0%)
failures:     0 (0.0%)
errors:       0 (0.0%)
total:        3 in 5(ms)
$ 

竜巻情報が出された。豹が降る恐れがあるとか言っている。

2012年8月12日日曜日

継承 (Inheritance)

前回のインスタンス関数のテーブル (table of instance function)の方法を活用して継承をやってみよう。

いつもの Integerクラスを継承して値を変更する機能を持つ IntegerVariableクラスをつくってみる。


ヘッダファイル (header file)

IntegerVariableクラスのヘッダを次に示す。
/* 11.Aug.2012  kei moses kissinger */

#ifndef _Included_IntegerVariable
#define _Included_IntegerVariable

typedef struct IntegerVariable* IntegerVariable;

typedef struct interface_IntegerVariable {

        /* Integer class */
        void (*finalize)(IntegerVariable this);
        int (*getValue)(IntegerVariable this);

        /* IntegerVariable class */
        void (*setValue)(IntegerVariable this, int value);
}* interface_IntegerVariable;

extern void static_IntegerVariable(interface_IntegerVariable interface_methods);

extern IntegerVariable new_IntegerVariable(int value);

extern void IntegerVariable_init(IntegerVariable this, int value);

extern void IntegerVariable_finalize(IntegerVariable this);

extern int IntegerVariable_getValue(IntegerVariable this);

extern void Integer_Variable_setValue(IntegerVariable this, int value);

#endif /* _Included_IntegerVariable */

基本的な構造は Integerクラスのものと同じである。
interface_IntegerVariableのメンバに setValue関数のポインタが追加されている。
注意が必要なのは、IntegerVariableで追加する関数よりも前の部分はスーパクラスのものと内容(関数ポインタの数とその順序)が同じでなければならない。そうでなければ呼び出したときに意図したものと異なる振る舞いをする。
ただし、スーパクラスで定義された関数であっても、thisポインタのクラス名だけは自クラスに置き換えている。これは呼び出し時にキャストを不要にするためである。
Cのキャストは何でも出来るため危険であり、キャストした内容を保証するのはプログラマの責任である。 softies projectでは呼び出しごとにキャストするのではなく、型変換を関数テーブルの初期化時に限定することで間違いを起こしにくくする戦略を採っている。 先ほどの関数テーブルのスーパクラス部分を同じにする注意点はキャストを安全に行うために必要である。
今までに無かった init関数はインスタンスを初期化する部分である。詳細はソースファイルのところで述べる。
IntegerVariableクラスで追加された setValue関数は勿論、継承する finalize, getValue関数も宣言し、各々 thisポインタは IntegerVariableクラスとしている。これは無くとも良いが、そうすると呼び出し側で毎回
this->interface_methods->getValue((Integer)this); 
のように記述しなければならなくなる。

ソースファイル (source file)

IntegerVariableクラスのソースを次に示す。
/* 11.Aug.2012  kei */

#include <IntegerVariable.h>
#include <Integer.h>

/****************************************************************
        private
****************************************************************/

#include <malloc.h>

struct IntegerVariable {
        interface_IntegerVariable interface_methods;

        /* Integer class */
        int value;  /* A bogus example. private value replaced with protected. */

        /* IntegerVariable class */
};

static struct interface_IntegerVariable interface_methods;

static void setValue(IntegerVariable this, int value) {
        this->value = value;
}

/****************************************************************
        public
****************************************************************/

void static_IntegerVariable(interface_IntegerVariable interface_methods) {
        /* ToDo: lock interface_methods */
        if (interface_methods->finalize == NULL) {
                static_Integer((interface_Integer)interface_methods);

                interface_methods->setValue = setValue;
        }
}

IntegerVariable new_IntegerVariable(int value) {
        static_IntegerVariable(&interface_methods);

        IntegerVariable this =
                (IntegerVariable)malloc(sizeof(struct IntegerVariable));

        if (this != NULL)
                IntegerVariable_init(this, value);

        return this;
}

void IntegerVariable_init(IntegerVariable this, int value) {
        Integer_init((Integer)this, value);
        this->interface_methods = &interface_methods;
}

void IntegerVariable_finalize(IntegerVariable this) {
        this->interface_methods->finalize(this);
}

int IntegerVariable_getValue(IntegerVariable this) {
        return this->interface_methods->getValue(this);
}

void IntegerVariable_setValue(IntegerVariable this, int value) {
        this->interface_methods->setValue(this, value);
}

冒頭では自身のヘッダ IntegerVariable.hとスーパクラスの Integer.hをインクルードしている。
IngerVariable.hの中ではスーパクラスが Integerクラスであることが明示されていないためである。(コメントに記述されているのみ。)
struct IntegerVariableでは、関数テーブルと同様に、プログラマの責任でスーパクラスの構造を引用しなければならない。 int valueをサブクラスでも記述しているてんがそれである。スーパクラスに属性を追加したとき、サブクラスも修正が必要であることを意味する。 ただしこの点の改善方法は別の機会に紹介する。
setValue関数が実装されているが、finalizeと getValueは Integerクラスを継承しているためこのクラスでは実装していない。
static_IntegerVariable関数は、以前示した static_Integerと同じように関数のポインタを関数テーブルに設定しているが、スーパクラスの関数を利用するために static_Integerを冒頭で呼び出している。
コンストラクタは new_IntegerVariableと IntegerVariable_initの2つの部分から成っている。new_IntegerVariableではインスタンスのためのメモリ割り当てを行うだけで初期化は IntegerVariable_initのほうで行っている。その訳はIntegerクラスの init関数のところで説明する。

あとから気づいたのだが、setValue関数にはインチキが含まれている。クラス図では Integerの valueは privateとしてあるが、これをサブクラスからアクセスしてるのだから protectedでなければならない。

Integerクラスの init関数 (init function of Integer class)

init関数を追加するために Integer.h, Integer.cを修正する。 (part of Integer.h, Integer.c)
extern Integer new_Integer(int value);

extern void Integer_init(Integer this, int value);

extern void Integer_finalize(Integer this);

Integer new_Integer(int value) {
        static_Integer(&interface_methods);

        Integer this = (Integer)malloc(sizeof(struct Integer));

        if (this != NULL)
                Integer_init(this, value);

        return this;
}

void Integer_init(Integer this, int value) {
        this->interface_methods = &interface_methods;

        this->value = value;
}

コンストラクタを newと initに分けた理由は、initをサブクラスの初期化からも呼び出したいからである。
newでメモリを確保して initを呼び出している。サブクラスではサブクラス自身の newでメモリ割り当てを行うため、スーパクラスの初期化だけを行う機能を呼び出したい。newと initの機能が一体になっていたのでは不要なメモリ割り当てが発生してしまうのである。

使用例 (example of use)

IntegerVariableクラスのテストケース IntegerVariableTest.cをつぎに示す。
/* 11.Aug.2012  kei */

#define import_lwd_unit_Assert
#define import_lwd_unit_CUnit

#include <lwd/unit/Assert.h>
#include <lwd/unit/CUnit.h>

#include <IntegerVariable.h>
#include <Integer.h>

/****************************************************************
        test cases
****************************************************************/

static IntegerVariable instance;

void setUp() {
        instance = new_IntegerVariable(811);
}

void tearDown() {
        IntegerVariable_finalize(instance);
}

void testNewAndFinalize() {
        /* setUp, tearDown */
}

void testGetValue() {
        int value = IntegerVariable_getValue(instance);
        Assert_equalsInt("A value must be 811", 811, value);
}

void testSetValue() {
        IntegerVariable_setValue(instance, 812);

        int value = IntegerVariable_getValue(instance);
        Assert_equalsInt("A value must be 812", 812, value);
}

void testInheritedGetValue() {
        Integer reference = (Integer)instance;
        int value = Integer_getValue(reference);
        Assert_equalsInt("A value must be 811", 811, value);
}

/****************************************************************
        main
****************************************************************/

int main(int argc, char** argv) {
        return CUnit_test(argc, argv);
}

testGetValue()と testInheritedGetValue()では各々自クラスとスーパクラスとして保持したインスタンスで継承をテストしている。

動作確認の様子をつぎに示す。 なお、コンストラクタを newと initの部分に分けた Integerクラスも回帰テスト (regression test) している。
$ make clean test
cc -g -finstrument-functions -I. -I /export/home/kei/c/core/include -I /export/home
/kei/c/experimental/include -I /export/home/kei/c/io/include -I /export/home/kei/c
/lang/include -I /export/home/kei/c/message/include -I /export/home/kei/c/unit/include
 -I /export/home/kei/c/util/include -I /export/home/kei/c/x11/include -I ../../char/
include -I ../../class1/include  -c -o IntegerTest.o IntegerTest.c
cc -g -finstrument-functions -I. -I /export/home/kei/c/core/include -I /export/home
/kei/c/experimental/include -I /export/home/kei/c/io/include -I /export/home/kei/c
/lang/include -I /export/home/kei/c/message/include -I /export/home/kei/c/unit/include
 -I /export/home/kei/c/util/include -I /export/home/kei/c/x11/include -I ../../char/
include -I ../../class1/include  -c -o IntegerVariableTest.o IntegerVariableTest.c
# IntegerTest #
[CUnit] ./IntegerTest: testGetValue
[CUnit] ./IntegerTest: testNewAndFinalize
[SUCCEED] testGetValue in 2(ms)
[SUCCEED] testNewAndFinalize in 2(ms)
successes:    2 (100.0%)
failures:     0 (0.0%)
errors:       0 (0.0%)
total:        2 in 4(ms)
# IntegerVariableTest #
[CUnit] ./IntegerVariableTest: testGetValue
[CUnit] ./IntegerVariableTest: testInheritedGetValue
[CUnit] ./IntegerVariableTest: testNewAndFinalize
[CUnit] ./IntegerVariableTest: testSetValue
[SUCCEED] testGetValue in 3(ms)
[SUCCEED] testInheritedGetValue in 1(ms)
[SUCCEED] testNewAndFinalize in 2(ms)
[SUCCEED] testSetValue in 2(ms)
successes:    4 (100.0%)
failures:     0 (0.0%)
errors:       0 (0.0%)
total:        4 in 8(ms)
$ 


おまえ、AKB48のファンだって?全員の名前言えるか? 俺なんか Perfumeの全員の名前スラスラ言えるぞ!

2012年8月9日木曜日

インスタンス関数のテーブル (table of instance function)

オブジェクト指向設計、プログラミング(Die objektorientierte design und programmierung)にとって重要な機構の 1つとして、汎化・特化(generalization - specialization)、あるいは継承(inheritance)がある。ここでは、継承を説明する前にその準備段階としてインスタンス関数をテーブル化する方法について説明する。 C++では仮想関数テーブル(vtable)と呼ばれているものである。 C++では仮想関数と非仮想関数を使い分けることができる。 softies projectの技法でも同様にこれらを使い分けることができるが、原則として全てのインスタンス関数は仮想関数であることが要求されている。

ヘッダファイル (header file)

これまでに何度か登場している Integerクラスを仮想関数に対応させるためのヘッダファイルをつぎに示す。
なお、前回実装した UpDownインターフェースは外している。
/* 4.7.9.Aug.2012       kei moses kissinger */

#ifndef _Included_Integer
#define _Included_Integer

typedef struct Integer* Integer;

typedef struct interface_Integer {
        void (*finalize)(Integer this);
        int (*getValue)(Integer this);
}* interface_Integer;

extern void static_Integer(interface_Integer interface_methods);

extern Integer new_Integer(int value);

extern void Integer_finalize(Integer this);

extern int Integer_getValue(Integer this);

#endif /* _Included_Integer */


仮想関数を使うためにヘッダファイルに必要になるのは interface_Integer構造体と、それを初期化するための static_Integer関数の2つだけである。

命名規則 (naming convention)では、クラス名の前にパッケージ名を付けると述べたが、この場合の接頭辞 interface, staticはパッケージ名ではなくキーワードである。 Cのキーワードとは異なる使い方のため、空白ではなく'_'で結合している。

また、関数テーブル interface_Integerは循環参照を使用しないことが明らかであるため、typedefと structの定義は分離していない。

ソースファイル (source file)

関数テーブルを使った Interfaceクラスのソースファイルを次に示す。
/* 4.7.9.Aug.2012       kei */

#include <Integer.h>

/****************************************************************
        private
****************************************************************/

#include <malloc.h>

struct Integer {
        interface_Integer interface_methods;

        int value;
};

static struct interface_Integer interface_methods;

static void finalize(Integer this) {
        free(this);
}

static int getValue(Integer this) {
        return this->value;
}

/****************************************************************
        public
****************************************************************/

void static_Integer(interface_Integer interface_methods) {
        /* ToDo: lock interface_methods */
        if (interface_methods->finalize == NULL) {

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

Integer new_Integer(int value) {
        static_Integer(&interface_methods);

        Integer this = (Integer)malloc(sizeof(struct Integer));

        if (this != NULL) {
                this->interface_methods = &interface_methods;

                this->value = value;
        }

        return this;
}

void Integer_finalize(Integer this) {
        this->interface_methods->finalize(this);
}

int Integer_getValue(Integer this) {
        return this->interface_methods->getValue(this);
}


関数テーブルを使用することによるヘッダファイルの変更量は小さいが、ソースファイルはかなり複雑になっている。

struct Integerの先頭のメンバに interface_methodsが追加されている。これが C++のvtableであり、OMTで struct ShapeClass * class; と書かれているものとほぼ同じである。

外部 static変数 interface_methodsは関数テーブルの実体である。インスタンスそれぞれに関数テーブルを配することもできるが、インスタンスが多くなるとその関数テーブルのためのメモリもその数分必要になってしまう。そこでクラスごとに関数テーブルを1個だけとし、インスタンスにはそれへのポインタのみを持つようにすることでメモリ(内存, memory)を節約してる。 なお、関数テーブルを constとしないのには訳がある。クラスの継承を行うときのために、関数テーブルを静的に初期化するのではなく、1個ずつ設定できるようにするためである。これは継承の説明をするときに明らかにする。

2つの static関数 finalize, getValueは各々これまでの実装での Integer_finalize, Integer_getValueに記述されていた、機能の実装である。 代わりに Integer_finalize, Integer_getValueは関数テーブル経由で finalize, getValue関数を呼び出している。 これは一見無駄に見えるかも知れないが、継承による多相性(多態性: polymorphism)を実現するために重要な仕組みである。

static_Integer関数では、methods_tableの初期化を行っている。 外部 static変数の methods_tableを直接初期化するのではなく、わざわざ引数で渡して物を初期化しているのも継承クラスの初期化にも使用するためである。継承の例は次回お見せすることになる。

methods_tableの初期化は 1回だけで良いため、finalizeが nullのときだけ設定を行なう。

この実装では、複数の threadから同時に呼ばれたときに、競合が生ずる。特に継承した場合には深刻な問題を起こす可能性がある。実際にはロックが必要。

この static_Integerはコンストラクタ new_Integerの先頭で呼び出している。単純にするために、このようにコンストラクタから毎回呼び出してるが、最初に関数テーブルが使用されるよりも前に確実に、1回だけ呼び出すことができればオーバヘッドは解消される。この点はまだ良い方法を見つけておらず、今後の課題である。

そして、インスタンス変数初期化時にインスタンスメンバの interface_methodsポインタに、interface_methods実体のアドレスを設定する。

ビルドはつぎの通り。
$ make clean install
rm -f "libclass1.a";
rm -f *.o;
cc -g -I /export/home/kei/c/core/include -I /export/home/kei/c/experimental/include
 -I /export/home/kei/c/io/include -I /export/home/kei/c/lang/include -I /export/home/
kei/c/message/include -I /export/home/kei/c/unit/include -I /export/home/kei/c/util/
include -I /export/home/kei/c/x11/include -I ../../char/include -I ../../class1/
include -D_REENTRANT  -c -o Integer.o Integer.c
ar cvrf "libclass1.a" Integer.o ;
a - Integer.o
mkdir -p /export/home/kei/sub/class1/lib;
libclass1.a updated.
$ 


回帰テスト(regression test)はつぎの通り。
$ make clean test
cc -g -finstrument-functions -I. -I /export/home/kei/c/core/include -I /export/home/
kei/c/experimental/include -I /export/home/kei/c/io/include -I /export/home/kei/c/
lang/include -I /export/home/kei/c/message/include -I /export/home/kei/c/unit/
include -I /export/home/kei/c/util/include -I /export/home/kei/c/x11/include -I ../
../char/
include -I ../../class1/include  -c -o IntegerTest.o IntegerTest.c
# IntegerTest #
[CUnit] ./IntegerTest: testGetValue
[CUnit] ./IntegerTest: testNewAndFinalize
[SUCCEED] testGetValue in 3(ms)
[SUCCEED] testNewAndFinalize in 1(ms)
successes:    2 (100.0%)
failures:     0 (0.0%)
errors:       0 (0.0%)
total:        2 in 4(ms)
$ 


softies projectとは (what is softies project)

softiesは "Stupid Object Framework TechnologIES" を表したもの。

何ら新しい技術を用いているわけではないが、プログラミング言語 Cでオブジェクト指向プログラミングの基本機構の幾つかを実現している。UMLで設計したものを Cで実装できることを目標にしている。

まったく馬鹿げており、人手でコーディングするのは現実的では無いと思われ、C++を使用すべき状況である。しかし、ここで紹介している全ての手法を同時に用いるのではなく、期待する効果を絞り、そのために必要な方法だけを真似していただければきっと役に立つものと思われる。


小原日登美に続き伊調馨の金で君が代が流れ

2012年8月8日水曜日

インターフェース (interface)

プログラミング言語 Cでインターフェース機能を使うことは難しくはない。

以前の例題の Interfaceクラスを少し改造して UpDownインターフェースを実装する例を示す。


インターフェースのヘッダ (header of interface)

UpDownインターフェースのヘッダはつぎの通り。
/* 7.Aug.2012 kei Moses Kissinger */

#ifndef _Included_UpDown
#define _Included_UpDown

typedef struct UpDown* UpDown;

struct UpDown {
        void (*up)(void* this);
        void (*down)(void* this);
};

#endif /* _Included_UpDown */

このように、構造体のメンバに関数ポインタを使用するのが一般的。

UpDownインターフェースは Integerクラス以外からも使えるため、引数の thisの型は void*にしてある。

もし Integerクラスでしか使用しないなら、わざわざインターフェースにする意味はほとんどないだろう。

実装クラスのヘッダ (header of implementation class)

UpDownインターフェースを実装する Integerクラスのヘッダファイルはつぎの通り。
/* 4.7.Aug.2012 kei */

#ifndef _Included_Integer
#define _Included_Integer

typedef struct Integer* Integer;

#include <UpDown.h>

extern UpDown Integer_interface_UpDown;

extern Integer new_Integer(int value);

extern void Integer_finalize(Integer this);

extern int Integer_getValue(Integer this);

extern void Integer_up(Integer this);

extern void Integer_down(Integer this);

#endif /* _Included_Integer */

Integer_interface_UpDownがインターフェースの構造体へのポインタ。

Integer_upと Integer_down関数はインターフェースを介さずにも呼べるようにするためのもの。インターフェース経由でしか呼ばないなら不要である。

実装 (implementation)

UpDownインターフェースを実装した Integerクラスのソースファイルはつぎの通り。
/* 4.7.Aug.2012 kei */

#include 

struct Integer {
        int value;
};

#include <malloc.h>

Integer new_Integer(int value) {
        Integer this = (Integer)malloc(sizeof(struct Integer));

        if (this != NULL) {
                this->value = value;
        }

        return this;
}

void Integer_finalize(Integer this) {
        free(this);
}

int Integer_getValue(Integer this) {
        return this->value;
}

void Integer_up(Integer this) {
        ++this->value;
}

void Integer_down(Integer this) {
        --this->value;
}

static struct UpDown interface_UpDown = {
        (void (*)(void*))Integer_up,
        (void (*)(void*))Integer_down
};

UpDown Integer_interface_UpDown = &interface_UpDown;

Integer_up, Integer_downは通常の public関数同様に記述しているが、up, downをインターフェース経由でしか利用しないなら、staticな関数として非公開にし、名前も increment, decrementのようなインターフェースの関数名とは異なるものにしても良い。その場合、関数名は他のソースファイルとは衝突する恐れが無いため、命名規約に沿った長い名前を付ける必要もない。

struct UpDownにて、upと down関数のポインタを設定している。 このとき、インターフェースの関数の型に合わせる為、キャストが必要となる。 ちょっと残念。

参考のため、ビルドの例も付けておく。
$ make install
cc -g -I /export/home/kei/c/core/include -I /export/home/kei/c/experimental/include
 -I /export/home/kei/c/io/include -I /export/home/kei/c/lang/include -I /export/home/
kei/c/message/include -I /export/home/kei/c/unit/include -I /export/home/kei/c/util/
include -I /export/home/kei/c/x11/include -I ../../char/include -I ../../class1/
include -D_REENTRANT  -c -o Integer.o Integer.c
ar cvrf "libclass1.a" Integer.o ;
r - Integer.o
mkdir -p /export/home/kei/sub/class1/lib;
libclass1.a updated.
$ 

なお、この方式では UpDown.cファイルは存在しない。

使用例 (example)

CUnitを使って使用例を示す。
/* 4.7.Aug.2012 kei */

#define import_lwd_unit_Assert
#define import_lwd_unit_CUnit

#include <Integer.h>
#include <lwd/unit/Assert.h>
#include <lwd/unit/CUnit.h>

static Integer instance;

void setUp() {
        instance = new_Integer(777);
}

void tearDown() {
        Integer_finalize(instance);
}

void testNewAndFinalize() {
        /* setUp and tearDown are invoked. */
}

void testGetValue() {
        int value = Integer_getValue(instance);

        Assert_equalsInt("A value must be 777.", 777, value);
}

void testUp() {
        UpDown upDown = Integer_interface_UpDown;
        upDown->up(instance);

        int value = Integer_getValue(instance);
        Assert_equalsInt("A value must be 778.", 778, value);
}

void testDown() {
        UpDown upDown = Integer_interface_UpDown;
        upDown->down(instance);

        int value = Integer_getValue(instance);
        Assert_equalsInt("A value must be 776.", 776, value);
}

int main(int argc, char** argv) {
        return CUnit_test(argc, argv);
}
testUp, testDown関数で各々 UpDownインターフェースを求め、up, down関数を呼び出している。
Integer_interface_UpDownのままでも up, down関数は呼べちゃうが、そうすると抽象化の意味は薄れる。

実際の使用場面では関数呼び出しや他のオブジェクトのメンバとして使われると思うが、この方式では常にインターフェースとインスタンスを組にして扱わなければならない。 つまり関数呼び出しではインターフェースとインスタンスで引数は 2個必要だし、クラスのメンバにするときも同様にインターフェースとインスタンスのメンバが必要となる。

これは単に 2個ずつペアにするという手間だけの問題ではない。 もしインターフェースとインスタンスが別々のクラスのものを組み合わせたとき、悲劇が起こるだろう。たとえば、同じ UpDownインターフェースを実装した Integerとは全く異なる GuiSliderクラスがあったとして、次のような呼び出しを考えてみて欲しい。
setObject(Integer_interface_UpDown, guiSlider);  /* very bad! */

参考のため実行例も示す。
$ make clean test
cc -g -finstrument-functions -I. -I /export/home/kei/c/core/include -I /export/home/
kei/c/experimental/include -I /export/home/kei/c/io/include -I /export/home/kei/c/
lang/include -I /export/home/kei/c/message/include -I /export/home/kei/c/unit/include
 -I /export/home/kei/c/util/include -I /export/home/kei/c/x11/include -I ../../char/
include -I ../../class1/include  -c -o IntegerTest.o IntegerTest.c
# IntegerTest #
[CUnit] ./IntegerTest: testDown
[CUnit] ./IntegerTest: testGetValue
[CUnit] ./IntegerTest: testNewAndFinalize
[CUnit] ./IntegerTest: testUp
[SUCCEED] testDown in 2(ms)
[SUCCEED] testGetValue in 2(ms)
[SUCCEED] testNewAndFinalize in 3(ms)
[SUCCEED] testUp in 2(ms)
successes:    4 (100.0%)
failures:     0 (0.0%)
errors:       0 (0.0%)
total:        4 in 9(ms)
$ 


softies projectでのインターフェース

softiesでは、インターフェースはこの方法よりもより手の込んだやり方で実現している。それにより、前述の問題を解決し、インスタンスからインターフェースを取り出せるようにしている。

ただし、CUnit frameworkだけはこの例に近い方法を用いている。そうすることにより、softiesの設計・コーディング規約に従わない普通の Cのプログラムも単体テストできるようになっている。

babe 鯨

2012年8月5日日曜日

クラスの定義 (definition of class)

つぎの図に示す Integerクラスを用いてプログラミング言語 Cでのクラス定義の典型的な例を紹介する。


属性として 1個の int値を持つクラスで、コンストラクタによってこの値が初期化される。

finalizeは オブジェクトを破棄する関数である。

getValueは 値を返す関数である。


ヘッダファイル (header file)

ヘッダファイル Integer.hはつぎのようになる。
/* 4.Aug.2012   kei */

#ifndef _Included_Integer
#define _Included_Integer

typedef struct Integer* Integer;

extern Integer new_Integer(int value);

extern void Integer_finalize(Integer this); 

extern int Integer_getValue(Integer this);

#endif /* _Included_Integer */
多重インクルード防止した上で、型の定義、コンストラクタや関数の宣言を記述する。

型定義については構造体(structure)と typedefで述べているように構造体名と同じ名前を定義し、ポインタも含めてある。

コンストラクタ名は接頭辞として new_を付けてある。命名規約にあるようにクラス名の後ではなく前につけてあるのは newはメンバ関数ではなく C++や Javaの new演算子をエミュレーションしたものだからである。

finalize, getValue関数は引数として自オブジェクトを渡す。この名称は thisを使用しているが、C++から利用する可能性があるならば、thisキーワードとの衝突を防止するために selfなどにするのが良いだろう。(OMT本では selfが使われている)

ソースファイル (source file)

ソースファイル Integer.cを次に示す。
/* 4.Aug.2012   kei */

#include <Integer.h>

struct Integer {
        int value;
};

#include <malloc.h>

Integer new_Integer(int value) {
        Integer this = (Integer)malloc(sizeof(struct Integer));

        if (this != NULL) {
                this->value = value;
        }

        return this;
}

void Integer_finalize(Integer this) {
        free(this);
}

int Integer_getValue(Integer this) {
        return this->value;
}
構造体の定義はヘッダファイルではなくソースファイルに記述することにより内部構造を隠蔽することができる。ただこの方法にはクラスを継承させようとしたときに、スーパクラスのインスタンスのサイズを得ることができなくなるデメリットもある。

コンストラクタでは malloc関数を用いてメモリを割り当て、インスタンス変数を初期化し、メモリのアドレスを returnする。

デストラクタでは free関数を用いてメモリを解放する。

getValue関数ではインスタンス変数の値を返却する。

これらから、オブジェクトを生成、利用、破棄がプログラム言語 Cでも実現できることが解かる。thisをコンストラクタで returnしたり、インスタンス関数の引数にすることを明示的に行わなければならない点がオブジェクト指向言語とは異なる。


ビルド (build)

ビルドしてみる。
$ make clean install
rm -f "libclass1.a";
rm -f *.o;
cc -g -I /export/home/kei/c/core/include -I /export/home/kei/c/experimental/include -I
 /export/home/kei/c/io/include -I /export/home/kei/c/lang/include -I /export/home/kei/
c/message/include -I /export/home/kei/c/unit/include -I /export/home/kei/c/util/
include -I /export/home/kei/c/x11/include -I ../../char/include -I ../../class1/
include -D_REENTRANT  -c -o Integer.o Integer.c
ar cvrf "libclass1.a" Integer.o ;
a - Integer.o
mkdir -p /export/home/kei/sub/class1/lib;
libclass1.a updated.
$  
この例では静的リンク用にアーカイブファイルを生成している。

いろいろインクルードバスを指定しているのは、sofities projectの各種 sub projectを参照しているものである。今回使用していない io, lang, message, util, x11が含まれているが、これらは別の機会に説明する。

単体テスト (unit test)

Integerクラスを利用する例として単体テストのコード IntegerTest.cを示す。
/* 4.Aug.2012 kei */

#define import_lwd_unit_Assert
#define import_lwd_unit_CUnit

#include <Integer.h>
#include <lwd/unit/Assert.h>
#include <lwd/unit/CUnit.h>

static Integer instance;

void setUp() {
        instance = new_Integer(777);
}

void tearDown() {
        Integer_finalize(instance);
}

void testNewAndFinalize() {
        /* setUp and tearDown are invoked. */
}

void testGetValue() {
        int value = Integer_getValue(instance);

        Assert_equalsInt("A value must be 777.", 777, value);
}

int main(int argc, char** argv) {
        return CUnit_test(argc, argv);
}

sofities projectの test frameworkを使用しているが、フレームワークの使い方は別の機会に説明する。

setUp, tearDownではコンストラクタとデストラクタを呼び出している。

getGetValue関数では getValueを呼び出している。

C++では instance->getValue()のように instanceオブジェクトに対して getValue関数を呼び出すことが理解しやすい記述ができる。Cでは getValue関数に instanceオブジェクトを渡す事しか表現できない。これがオブジェクト指向言語かどうかの違いの1つである。


ついでに、テストの実行の様子も残しておこう。
$ make clean test
cc -g -finstrument-functions -I. -I /export/home/kei/c/core/include -I /export/home/
kei/c/experimental/include -I /export/home/kei/c/io/include -I /export/home/kei/c/lang
/include -I /export/home/kei/c/message/include -I /export/home/kei/c/unit/include -I
 /export/home/kei/c/util/include -I /export/home/kei/c/x11/include -I ../../char/
include -I ../../class1/include  -c -o IntegerTest.o IntegerTest.c
# IntegerTest #
[CUnit] ./IntegerTest: testGetValue
[CUnit] ./IntegerTest: testNewAndFinalize
[SUCCEED] testGetValue in 3(ms)
[SUCCEED] testNewAndFinalize in 2(ms)
successes:    2 (100.0%)
failures:     0 (0.0%)
errors:       0 (0.0%)
total:        2 in 5(ms)
$ 


撚ってる私のハート