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 とし、書き換えられないようにした。