Cでオブジェクト指向フレームワークを作る (1):ポインタの準備
よく,解説記事などで,オブジェクト指向の三本柱は「継承,カプセル化,多態」なのだとか言われている。そのうちの1つ,多態を上手く扱うためにC++にあって,Cに無い機能が,ユーザー定義型の暗黙の型変換である。地味だけど重要な機能である。どういうことか言葉で説明するのは逆に分かりにくいので,簡単にコードで示す。今日の課題は
class A {}; class B : public A {}; : B* b = new B; A* a = b; // OK
のようなことをCで実現すること。もちろん,全部void*にして注意深くコーディングするという選択肢もあるけれど,私はunion を使うのが正解なのだろうと思う。
struct A; struct B; typedef union A_p_u A_p_t; typedef union B_p_u B_p_t; union A_p_u { void* ptr; A* self; }; union B_p_u { A_p_t a; B* self; }; : { A_p_t a; B_p_t b; a.self = ...; b.a = a; }
みたいにすれば,なんとかなる。
ポインタ型にはもう1つ重要な機能として,非const型からconst型への暗黙の型変換というものがある。上記のやり方にconstポインタ版を追加する。
union A_const_p_u { const void* ptr; const A* self; }; union B_const_p_u { A_const_p_t a; const B* self; };
それでもって,A_p_u,B_p_u の定義を
union A_p_u { void* ptr; A* self; A_const_p_u cnst; }; union B_p_u { A_p_t a; B* self; B_const_p_u cnst; };
などと書くと,C++の
B* b = ...;
const B* cb = b;
に相当するコードは
B_p_t b; B_const_p_t cb; b = ...; cb = b.cnst;
なんて書ける。cnst を左辺値に使うと C++ の const_cast に対応する。それと同じで,b = static_cast(a) は b.a = a と書ける。
さらに,もう1つ,C++ではあまり重要じゃないけど,Cのポインタで重要な機能として任意のポインタ型から void* への暗黙の型変換がある。実はこれは上記の定義に織り込み済みで,C++の
B* b = ...;
void* v = b;
に相当するコードは
B_p_t b; void* v; b = ...; v = b.a.ptr; /*あるいは単に v = b.self*/
という風になる。暗黙の型変換が明示的な記述になってしまっているんで,少々見づらいけど,逆にC++がどれだけ賢いことをやっているのかが浮き彫りになって面白い。
B* b = ...; const void* v = b;
は,
B_p_t b; const void* v; b = ...; v = b.a.cnst.ptr; /*あるいは b.cnst.a.ptr,または b.cnst.self */
となる。これが,Cのオブジェクト指向(のための)ポインタの記述法である,たぶん。副産物として,このポインタを使うとCの危険な仕様の1つである,void* から,あらゆるポインタ型への暗黙の型変換が禁止できる。
ここまで読んで,「なかなか使える」と思う人は少数で「うってなる」人が大多数だと半ば気付きながらも,ポインタの準備を終わりにしようと思う。
(つづく)