Cでオブジェクト指向っぽいことをする。

また社内勉強会用のネタ。C言語オブジェクト指向プログラムを試みる。Cはオブジェクト指向プログラムをサポートしていないのでいろいろ面倒くさいことになる。またオブジェクト指向プログラムはポインタを使いまくることになるのでガベージコレクタが無いCだとメモリリークが発生しないように神経を使うことになる。


JavaがよくわからないからCでオブジェクト指向をしようというのはおすすめできない。しかしオブジェクト指向プログラムで設計すると、モジュール分割の考え方ができるようになる気がする。


一応元ネタは結城浩さんのamazon:Java言語で学ぶデザインパターン入門を使わせていただく。初っぱなはイテレータパターンだが、まずは手始めにBookクラスをCで実装する。Javaでのコードは以下。フィールドはひとつで、メソッドも書名を返すだけの小さなクラスだ。

public class Book {
    private String name;
    public Book(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

このコードをCに落とす。Cの場合はヘッダファイルとそれを実装したソースの対になる。まずはヘッダファイル

#ifndef BOOK_HEADER_FILE_INCLUDED__
#define BOOK_HEADER_FILE_INCLUDED__

/*! Book構造体 */
typedef struct Book_tag Book;

/*!
	@brief	Bookコンストラクタ
	@param	pName: 書籍名
	@retval	生成されたBookオブジェクトを指すポインタ
	@attention	生成したオブジェクトはDisposeBookで破棄すること
 */
Book *CreateBook(const char *pName);

/*!
	@brief	Bookデストラクタ
	@param	pBook: Bookオブジェクトを指すポインタ
	@retval	無し
	@attention
 */
void DisposeBook(Book *pBook);

/*!
	@brief	書籍名を返す
	@param	pBook: Bookオブジェクトを指すポインタ
	@retval	書籍名
	@attention	戻り値で得たポインタが指す領域を書き換えないこと
 */
const char*const GetName(Book *pBook);

#endif

ポイントはBook構造体の宣言。ヘッダファイルでは不完全型の宣言とし、ポインタだけが見えるようにする。具体的なメンバ変数が見えてしまうとカプセル化ができない。Cはアクセス管理がファイルスコープ、ブロックスコープ、グローバルの3段階しかない。そのためヘッダファイルには不完全型の宣言のみを置き、メンバ変数は見せない。パブリックメンバ変数は持たないものとする。


もう一つ、ガベコレが無いのでデストラクタに当たる関数を用意する。
次にこのBookの実装側のファイル。

#include "Book.h"
#include <string.h>
#include <stdlib.h>

/*! Book構造体 */
struct Book_tag {
	char *name;	/*!< 書籍名  */
};

Book *CreateBook(const char *pName)
{
	Book *pBook = NULL;

	pBook = malloc(sizeof(Book));
	if (pBook == NULL) {
		goto ERROR;
	}

	pBook->name = NULL;
	
	if (pName == NULL) {
		pBook->name = malloc(1);
		if (pBook->name) {
			goto ERROR;
		}
		pBook->name[0] = '\0';
	} else {
		pBook->name = malloc(strlen(pName) + 1);
		if (pBook->name == NULL) {
			goto ERROR;
		}
		strcpy(pBook->name, pName);
	}

	return pBook;
	
ERROR:
	if (pBook != NULL) {
		free(pBook->name);
	}

	free(pBook);
	return NULL;
}
void DisposeBook(Book *pBook)
{
	if (pBook != NULL) {
		free(pBook->name);
	}
	free(pBook);
}

const char*const GetName(Book *pBook)
{
	const char *const retName = (pBook)? pBook->name: NULL;

	return retName;
}

ポインタが非常に多く出てくるのがわかると思う。GetName関数は戻り値をchar *const とし、書き換えられないようにした。