<<戻る

2004年11月11日

C言語でオブジェクト指向を思考する。

 

はじめに

オブジェクト指向の機能を持たない言語に、自ら実装を思考することでオブジェクト指向を理解します。またこの記事は解答ではなく思考することが命題です。基本的には検索エンジン(ex,google)などで解答を探さず、もっている知識のみで思考するため私個人以外は参考にならないと思います。

 

何をするか確認

まず、オブジェクト指向の3要素を実装できるか検討します。

 

思考その1

構造体はJavaなどのクラスに近いため構造体をクラスにしてカプセル化を実現してみます。

 

その前に、関数のポインタを再確認

/* 
 * 構造体のメンバに関数を割り当ててみる
 * 
 * オブジェクト指向言語では、オブジェクトに情報と処理が含まれているので
 * それをC言語で実現してみる。
 */ 


#include <stdio.h>

void message_hello() {
	printf("hello\n");
}

void method( void (*pFunc)(void) )
{
	(*pFunc)();
}

typedef struct UserData {
	int  age;
	void *getAge;
	void *setAge;
	void *message;
} USER_DATA;


int main(int argc, char *argv[])
{
	/* メモリ割り当て */
	USER_DATA okita;
	okita.age = 26;

	/* 関数のポインタ変数をメモリ割り当て */
	void(*tmp)(void);

	/* 構造体USER_DATAのokitaのメンバmessageに関数のポインタを割り当て */
	okita.message = message_hello; 

	/* message_hello関数とokita.messageメンバ変数が同じメモリを示している
     * 事を確認する。
     */
	printf("%p\n", okita.message);
	printf("%p\n", message_hello);

	/* ポインタ変数にもアドレスを渡す */
	tmp = (void(*)(void)) okita.message;

	/* message_hello関数と同じメモリを示している事を確認する。*/
	printf("%p\n", tmp);

	/* ポインタ変数から関数を実行 */
	(*tmp)();

	/* 構造体のメンバ変数から関数の実行 */
	(* ((void(*)(void)) okita.message))();

	return 0;
}

 

/*
 * 構造体に関数を割り当ててみる。
 *
 * 今回は、引数と戻り値を持っている関数を割り当ててみる 
 */
#include 

/* 戻り値のある関数 */
int returnOnly() {
	printf("returnOnly\n");
	return 1;
}

/* 引数のある関数 */
void argmentOnly(int i) {
	printf("argmentOnly\n");
	printf("%d", i);
}

/* 戻り値と引数のある関数 */
char *message(char *message) {
	printf("%s", message);
	return message;
}

typedef struct user_data {
	int age;
	void *method1;
	void *method2;
} USER_DATA;


int main(int argc, char *argv[]) {
	USER_DATA hoge;
	hoge.age = 0;
	char *returnMsg = NULL;

	/* 関数のポインタ型 */
	void(*fuga)(int);

	hoge.method1 = argmentOnly;
	hoge.method2 = message;

	printf("%p\n", argmentOnly);
	printf("%p\n", hoge.method1);

	printf("%p\n", message);
	printf("%p\n", hoge.method2);

	fuga = (void(*)(int))hoge.method1;

	/* 関数ポインタにアドレスを格納してからargmentOnly関数を呼び出す */
	fuga(10);

	/* hoge.method1のアドレスからargmentOnly関数を呼び出す */
	((void(*)(int))hoge.method1)(10);

	/* 戻り値と引数のある関数を間接的に呼び出す */
	returnMsg = ((char *(*)(char *))hoge.method2)("HELLO INDIRECT C WORLD!!");

	return 0;
}

上記の関数ポインタとヘッダファイルを組み合わせてメンバ変数を隠蔽しつつメンバ関数を使えるように実装してみます。

 

class.h

#include <stdio.h>

typedef struct __UserDataClass UserDataClass;
int getAge(UserDataClass *obj);
void setAge(UserDataClass *obj, int i);
UserDataClass *new(void);

 

class.c

#include "class.h"
#include <stdlib.h>


struct __UserDataClass {
	int age;
	int(*_getAge)(UserDataClass *obj);
	void(*_setAge)(UserDataClass *obj, int i);
};

int getAge(UserDataClass *obj) {
	return (int)(obj->_getAge);
}

void setAge(UserDataClass *obj, int i) {
	(obj->_setAge)(obj, i);
}

/* constructor */
UserDataClass *new(void)
{
	return (UserDataClass *) malloc(sizeof(UserDataClass));
}

void delete(UserDataClass *target)
{
	free(target);
}

main.c

#include "class.h"

int main(int argc, char *argv[])
{
	printf("hello\n");
	return 0;
}

[s-okita@localhost object_oriented]$ gcc -Wall -O2 -o main class.c main.c -I.

 

 

次にコンパイルは正常に実行できたので、これにもう1つクラスを追加して継承を実装してみようと思います。

 [s-okita@localhost object_oriented]$ cat class.c
#include "class.h"
#include <stdlib.h>

/*
 * 基本となるクラスを実装する
 */
struct __BaseClass {
                int age;
                int(*_getAge)(BaseClass *obj);
                void(*_setAge)(BaseClass *obj, int i);
};

int getAge(BaseClass *obj) {
                return (int)(obj->_getAge);
}

void setAge(BaseClass *obj, int i) {
                (obj->_setAge)(obj, i);
}

/* constructor */
BaseClass *new(void)
{
                return (BaseClass *) malloc(sizeof(BaseClass));
}

/* destructor */
void delete(BaseClass *target)
{
                free(target);
}


/*
 * ユーザデータクラス
 */
struct __UserDataClass {
                /* 継承するクラスをこのsuper変数に格納する */
                void *super;
                int age;
                int(*_getAge)(UserDataClass *obj);
                void(*_setAge)(UserDataClass *obj, int i);
};

int getAge(UserDataClass *obj) {
                return (int)(obj->_getAge);
}

void setAge(UserDataClass *obj, int i) {
                (obj->_setAge)(obj, i);
}

/* constructor */
UserDataClass *new(void)
{
                return (UserDataClass *) malloc(sizeof(UserDataClass));
}

/* destructor */
void delete(UserDataClass *target)
{
                free(target);
}

ここでコンパイルすると、当然ですが、C言語では関数名は一意(Unique)でなければならないのでコンパイルエラーが発生します。
[s-okita@localhost object_oriented]$ gcc -Wall -O2 -o main class.c main.c -I. In file included from class.c:1: class.h:10: conflicting types for `getAge' class.h:4: previous declaration of `getAge' class.h:11: conflicting types for `setAge' class.h:5: previous declaration of `setAge' class.h:12: conflicting types for `new' class.h:6: previous declaration of `new' class.h:13: conflicting types for `delete' class.h:7: previous declaration of `delete' class.c:13: conflicting types for `getAge' class.h:10: previous declaration of `getAge' class.c:17: conflicting types for `setAge' class.h:11: previous declaration of `setAge' class.c:23: conflicting types for `new' class.h:12: previous declaration of `new' class.c:29: conflicting types for `delete' class.h:13: previous declaration of `delete' class.c:45: conflicting types for `getAge' class.c:13: previous declaration of `getAge' class.c:49: conflicting types for `setAge' class.c:17: previous declaration of `setAge' class.c:55: conflicting types for `new' class.c:23: previous declaration of `new' class.c:61: conflicting types for `delete' class.c:29: previous declaration of `delete' In file included from main.c:1: class.h:10: conflicting types for `getAge' class.h:4: previous declaration of `getAge' class.h:11: conflicting types for `setAge' class.h:5: previous declaration of `setAge' class.h:12: conflicting types for `new' class.h:6: previous declaration of `new' class.h:13: conflicting types for `delete' class.h:7: previous declaration of `delete'

 

そこで、関数を共通化します。今までは以下のように、対象の引数がUserDataClass型かBaseClass型と書いていました。

void setAge(BaseClass *obj, int i) {
                (obj->_setAge)(obj, i);
}
void setAge(UserDataClass *obj, int i) {
(obj->_setAge)(obj, i);
}
  C言語でvoid *型は何でも入る型なのでこれをjava.lang.ObjectやObjective-Cのid型と見立てます。 void setAge(void *obj, int i) { } [s-okita@localhost object_oriented]$ cat class.c #include "class.h" #include /* * 基本となるクラスを実装する */ struct __BaseClass { int age; int(*_getAge)(BaseClass *obj); void(*_setAge)(BaseClass *obj, int i); }; /* * ユーザデータクラス */ struct __UserDataClass { /* 継承するクラスをこのsuper変数に格納する */ void *super; int age; int(*_getAge)(UserDataClass *obj); void(*_setAge)(UserDataClass *obj, int i); }; int getAge(void *obj) { return (int)(obj->_getAge); } void setAge(void *obj, int i) { (obj->_setAge)(obj, i); } /* constructor */ void *new(size_t classSize) { return (void *) malloc(classSize); } /* destructor */ void delete(void *target) { free(target); } [s-okita@localhost object_oriented]$ gcc -Wall -O2 -o main class.c main.c -I. class.c: In function `getAge': class.c:25: warning: dereferencing `void *' pointer class.c:25: request for member `_getAge' in something not a structure or union class.c: In function `setAge': class.c:29: warning: dereferencing `void *' pointer class.c:29: request for member `_setAge' in something not a structure or union

void *型のままだコンパイルエラーとなるのでどうにかして動的に構造体(クラス)を特定しなければならない。

 

 

 

ちょっと躓きそうなのでUnix/Linuxのシグナルをテスト実装してみます。