見出し画像

C言語でオブジェクト指向プログラミング~②継承の検証

はじめに

非オブジェクト指向プログラミング言語であるC言語を使用して、オブジェクト指向の3大要素である「カプセル化」「継承」「ポリモーフィズム」をどこまで実現可能かを検証してみます。
今回は、継承の実現について検証しています。

方針

C言語の文法には構造体はありますが、クラスはありません。ここでクラスとは、要素と、要素に対しての振る舞いを定義したものであるとします。構造体では要素を持てますが、要素に対する振る舞いを持つことは出来ません。そこで、構造体の持つ要素に対して振る舞いを行う関数を用意し、その関数のポインタを構造体に持たせることで、構造体が振る舞いを持ったような感じにします。

環境

Windows 10 Pro機にEclipse(厳密にはPleiades)を導入して、検証します。

フォルダ構成とファイル

今回の検証で使用した、フォルダ構成とファイルの配置です。

[プロジェクトrootフォルダ]
┣srcフォルダ
┃ ┣main.c
┃ ┣t_base.c
┃ ┗t_ext1.c
┗includeフォルダ
  ┣t_base.h
  ┗t_ext1.h

BASEクラスと、BASEクラスを継承したEXT1クラスを使用して、検証します。

t_base.h

#ifndef T_BASE_H_
#define T_BASE_H_
typedef struct _base TBASE, *BASE;
struct _base_member;
struct _base {
	struct _base_member* m;
	void (*setNumber)(BASE, long);
	long (*getNumber)(BASE);
	void (*addNumber)(BASE);
	void (*subNumber)(BASE);
};
BASE new_BASE(void);
void delete_BASE(BASE this);
void construct_BASE(BASE);
void destructor_BASE(BASE);
#endif /* T_BASE_H_ */

BASEクラスの定義ファイルです。


t_ext1.h

#include "../include/t_base.h"
#ifndef T_EXT1_H_
#define T_EXT1_H_
typedef struct _ext1 *EXT1;
struct _ext1_member;
struct _ext1 {
	TBASE my;		    // オーバーライド用
	TBASE super;		// superクラス参照用
	struct _ext1_member* m;
	void (*setString)(EXT1, char*);
	char* (*getString)(EXT1);
};
EXT1 new_EXT1(void);
void delete_EXT1(EXT1 this);
void construct_EXT1(EXT1);
void destructor_EXT1(EXT1);
#endif /* T_EXT1_H_ */

BASEクラスを継承したEXT1クラスの定義ファイルです。
BASEクラスを継承するにあたり、BASEクラス自身を持つ必要があります。その際に、オーバーライドする場合と、EXT1クラス内でBASEクラスの振る舞いを使用する場合(superとして使用する場合)の2パターンに備えて、以下のようにBASEクラスの要素を2つ持っています。

	TBASE my;		    // オーバーライド用
	TBASE super;		// superクラス参照用

ここでは、superよりもmyを先に持たせているのがポイントです。


t_base.c

#include <stdio.h>
#include <stdlib.h>
#include "../include/t_base.h"
struct _base_member {
	long number;
};
static void setNumber(BASE this, long number);
static long getNumber(BASE this);
static void addNumber(BASE this);
static void subNumber(BASE this);
BASE new_BASE(void) {
	BASE this;
	this = (BASE)malloc(sizeof(struct _base));
	return this;
}
void construct_BASE(BASE this) {
	this->m = (struct _base_member *)malloc(sizeof(struct _base_member));
	this->setNumber = setNumber;
	this->getNumber = getNumber;
	this->addNumber = addNumber;
	this->subNumber = subNumber;
}
void destructor_BASE(BASE this) {
	free(this->m);
}
void delete_BASE(BASE this){
	destructor_BASE(this);
	free(this);
}
void setNumber(BASE this, long number){
	this->m->number = number;
}
long getNumber(BASE this){
	return this->m->number;
}
void addNumber(BASE this){
	this->m->number++;
}
void subNumber(BASE this){
	this->m->number--;
}

BASEクラスの中身です。

t_ext1.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../include/t_ext1.h"
struct _ext1_member {
	char* string;
};
static void setNumber(EXT1, long);
static void setString(EXT1, char*);
static char* getString(EXT1);
EXT1 new_EXT1(void) {
	EXT1 this;
	this = (void*)malloc(sizeof(struct _ext1));
	return this;
}
void construct_EXT1(EXT1 this){
	construct_BASE(&(this->my));
	construct_BASE(&(this->super));
	this->m = (struct _ext1_member *)malloc(sizeof(struct _ext1_member));
	this->my.setNumber = setNumber;
	this->setString = setString;
	this->getString = getString;
}
void destructor_EXT1(EXT1 this){
	destructor_BASE(&(this->super));
	destructor_BASE(&(this->my));
	free(this->m);
}
void delete_EXT1(EXT1 this){
	destructor_EXT1(this);
	free(this);
}
void setNumber(EXT1 this, long number){
	if (number > 10) {
		this->super.setNumber(this, 10);
	}
	else {
		this->super.setNumber(this, number);
	}
}
void setString(EXT1 this, char* string){
	this->m->string = string;
}
char* getString(EXT1 this){
	return this->m->string;
}

EXT1クラスの中身です。
ここでは、setNumberメソッドをオーバーライドしています。
また、EXT1クラス独自の要素としてstringを持ち、これを隠蔽しています。


main.c

#include <stdio.h>
#include <stdlib.h>
#include "../include/t_base.h"
#include "../include/t_ext1.h"

int main(void) {

	BASE base = new_BASE();
	EXT1 ext1 = new_EXT1();

	construct_BASE(base);
	construct_EXT1(ext1);

	base->setNumber(base, 1);
	((BASE)ext1)->setNumber(ext1,11);
	printf("[L.%d] %ld\n", __LINE__, base->getNumber(base));
	printf("[L.%d] %ld\n", __LINE__, ((BASE)ext1)->getNumber(ext1));

	base->subNumber(base);
	printf("[L.%d] %ld\n", __LINE__, base->getNumber(base));
	printf("[L.%d] %ld\n", __LINE__, ((BASE)ext1)->getNumber(ext1));

	((BASE)ext1)->addNumber(ext1);
	printf("[L.%d] %ld\n", __LINE__, base->getNumber(base));
	printf("[L.%d] %ld\n", __LINE__, ((BASE)ext1)->getNumber(ext1));

	ext1->setString(ext1, "Hallo! Welt!");
	printf("[L.%d] %s\n", __LINE__, ext1->getString(ext1));

	delete_BASE(base);
	delete_EXT1(ext1);

	return EXIT_SUCCESS;
}

BASEクラスのbaseオブジェクトでsetNumberやsubNumberした結果は、baseオブジェクトにのみ反映される予想です。
一方、EXT1クラスのext1オブジェクトでsetNumberやaddNumberした結果は、ext1オブジェクトのみに反映される予想です。
と同時に、BASEクラスの振る舞いであるsetNumberやaddNumberを、ext1オブジェクトで使用可能である、という予想です。

また、EXT1クラス独自の振る舞いsetStringとgetStringが、ext1オブジェクトで使用可能である、という予想です。


実行結果

ビルドして実行してみます。

[L.16] 1
[L.17] 10
[L.20] 0
[L.21] 10
[L.24] 0
[L.25] 11
[L.28] Hallo! Welt!

想定通りの結果となりました。


終わりに

今回の検証ではビルド時に、複数の警告が発生しました。いずれも型不一致に関する内容でした。プロジェクトのコーディング規約で、型不一致の警告発生を許可していない場合は、この方法を採れませんので、その点はご了承ください。

親クラスの振る舞いの呼び出し方には、実は2パターンあります。1つは今回の検証内容ですが、親クラスにキャストして呼び出す方法です。

((BASE)ext1)->setNumber(ext1,11);

もう1つは以下のように、my要素を明示して呼び出す方法です。

ext1.my->setNumber(ext1,11);

後者の方法だと、継承の階層が深くなった場合に、myと書く回数が増えます。また、そのmyがどのクラスを指したmyなのかが分かりにくくなります。一方で前者の方法だと、myと書く必要がないため、前者に比べて可読効率が上がると思います。また、必ずキャストが発生することで、その振る舞いがどのクラスの振る舞いのものであるかを、視覚的に確認できます。

次回は、C言語で多様性の実現について検証してみます。





この記事が気に入ったらサポートをしてみませんか?