空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

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* から,あらゆるポインタ型への暗黙の型変換が禁止できる。

ここまで読んで,「なかなか使える」と思う人は少数で「うってなる」人が大多数だと半ば気付きながらも,ポインタの準備を終わりにしようと思う。

(つづく)