この日記はGNSで生成しています。 |
_ こういった疑問って、コンパイラが吐くソースの身になるとすぐ解消できたりする。きっと、こういうコンパイル結果を吐いているはず・・・Cで書いてみる。
void AA_hello_void(void) { /* "I am AA"を表示 */ } void AA_hello_int(int i) { /* "I am AA"+iを表示 */ } typedef char *string; void CC_hello_string(string p) { /* "I am AA"+pを表示 */ } typedef struct { void (*AA_hello_void)(void); void (*AA_hello_int)(int); } T_AA; T_AA AA = { AA_hello_void, AA_hello_int }; typedef struct { void (*AA_hello_void)(void); void (*AA_hello_int)(int); void (*CC_hello_string)(string); } T_CC; T_CC CC = { AA_hello_void, AA_hello_int, CC_hello_string }; main() { T_AA *obj; obj = malloc( sizeof(T_AA) ); memcpy( obj, CC, sizeof(T_AA) ); /* ここまでが「AA obj=new CC();」に相当 */ obj->AA_hello_int(256); obj->AA_hello_void(); obj->CC_hello_string(); /* ←ここでエラーになるはず */ }
_ クラスのメソッドの羅列のコンパイル結果ってのは、ただの「ジャンプテーブル」でしかない。継承はぶっちゃけた話「元からあるテーブルに追加して新しいテーブルにする行為」でしかない。objが型AAで定義したにもかかわらず型CCを代入できるのは「CCはAAに追加したものであるから、つまりAAでもある」からで、CCで追加した分は代入されない。と考えれば、CCで追加したメソッドをAAの型のままで呼べるはずがないことは、明らか。
_ 仮想関数ってのは、
T_AA AA = { AA_hello_void, AA_hello_int };
の「AA_hello_?」の部分にNULLをとりあえず入れておく行為だと思えば、なぜコンパイルが通るようになったかもわかるはず。
_ 補足。ここではわかりやすくするために「ジャンプテーブルは中身をコピーする」ような記述にしたけど、実際はどっかに中身があって、「中身へのポインタ」しかコピーしない・・・でないと実行効率が悪すぎるはず。この場合、「CCで追加した分は代入されない」ということが嘘になってしまうが、「T_CCへのポインタをT_AAへのポインタにキャストした時点で、追加分が見えなくなる」ことで事実上同じ結果になっている・・・はず。
メールはこちらへ...[後藤浩昭 / Hiroaki GOTO / GORRY / gorry@hauN.org]