Opaqueポインタについて
Opaqueポインタ
インターフェース上で未定義のデータ型をopaque data type(不透明型)と呼び、そのような型を指すポインタをOpaqueポインタと呼びます。例えばヘッダで定義されている以下のようなポインタのことを指します。
MyInterface.h
#ifndef IF_H #define IF_H typedef struct tag_mydata* mydata_t; #endif
Opaqueポインタを使った例をいくつか取り上げたいと思います。
Opaqueポインタを使ってデータ型を隠蔽する
独自フォーマットの構造体がインターフェース上で定義されているとします。もしフォーマットを修正するとヘッダをインクルードしている部分は再度コンパイルしなおさなければなりません。特にフォーマットが非常に基本的な型であればあるほど影響範囲が広くなると思います。
MyFormat.h
static const unsigned long MY_FORMAT_TAG_SIZE = 4; typedef struct tag_myformat{ char tag[MY_FORMAT_TAG_SIZE]; unsigned long chankSize; }*myformat_t; void setMyFormat(myformat_t format);
MyFormat.c
#include "MyFormat.h" #include <string.h> // size_t, strcpy void setMyFormat(myformat_t format){ strcpy(format->tag, "data"); format->chankSize = MY_FORMAT_TAG_SIZE + sizeof(format->chankSize); }
そこで、ヘッダで定義されているフォーマットの型をOpaqueポインタに修正し、実装側で構造体を定義することにしてみます。
MyFormat.h
typedef struct tag_myformat *myformat_t; void setMyFormat(myformat_t format);
MyFormat.c
#include "MyFormat.h" #include <string.h> // size_t, strcpy static const size_t MY_FORMAT_TAG_SIZE = 4; struct tag_myformat{ char tag[MY_FORMAT_TAG_SIZE]; size_t chankSize; }; void setMyFormat(myformat_t format){ strcpy(format->tag, "data"); format->chankSize = MY_FORMAT_TAG_SIZE + sizeof(format->chankSize); }
型定義を実装側に移すことで型の変更・修正とともに手を加えなければならないファイルがMyFormat.cだけで済みますので先ほどより簡単になります。また型を外部から隠蔽したい場合にもこの手法で実現できることが分かります。
void*型の代用として利用する
Opaqueポインタを利用すると、指し示しているデータ型を外部から隠蔽することができますので、C言語をオブジェクト指向的に実装することができます。全ての型を表現するのにvoid*型がよく利用されますがこれをインターフェース上で定義するとどうなるでしょうか?
例えば次のような例を考えてみます。
FrameBuffer.h
typedef void *HANDLE; typedef void *HINSTANCE; HANDLE createFramwBuffer(); void destroyFrameBuffer(HANDLE handle);
FrameBuffer.c
struct HANDLE__ { int windowId; }; struct HINSTANCE__ { int dummy; }; static int ID_COUNTER = 0; HANDLE createFramwBuffer(){ HANDLE handle = (HANDLE)malloc(sizeof(struct HANDLE__)); HINSTANCE inst = handle; struct HANDLE__* casted_handle = (struct HANDLE__*)handle; casted_handle->windowId = (ID_COUNTER++); return handle; } void destroyFrameBuffer(HANDLE handle){ if( handle == NULL ){ free(handle); } }
まずはvoid*を引数にして受け取ってから独自フォーマットにキャストしなければならないという点があります。そして、例えば別の型を導入した場合、実装側では別の型であるにも関わらず、お互いvoid*型なので誤って代入してしまう危険性があります。そこでOpaque型を使って以下のように修正します。
FrameBuffer.h
typedef struct HANDLE__ *HANDLE; typedef struct HINSTANCE__ *HINSTANCE; HANDLE createFramwBuffer(); void destroyFrameBuffer(HANDLE handle);
FrameBuffer.c
#include "FrameBuffer.h" #include <stdlib.h> struct HANDLE__ { int windowId; }; struct HINSTANCE__ { int dummy; }; static int ID_COUNTER = 0; HANDLE createFramwBuffer(){ HANDLE handle = (HANDLE)malloc(sizeof(struct HANDLE__)); HINSTANCE inst = handle; // 型の違いを認識可能→警告 handle->windowId = (ID_COUNTER++); return handle; } void destroyFrameBuffer(HANDLE handle){ if( handle == NULL ){ free(handle); } }
こうすれば、具体的な型が未定義であっても、お互いに異なることは認識できるので、上で述べた危険性を回避することができます。またキャストせずにそのまま利用することもできます。
フォーマットが変わる可能性のあるデータ型を扱うようなライブラリを実装する際にはこのような手法が有効かもしれません。