

普段、Smalltalk で遊んでいるだけだと、メンタルモデルが単純になりすぎていけません(^_^;)。 ボケ防止を兼ねて、基本である（…と個人的に考える）上記三言語における継承の例を書いて動かしてみたので結果をメモ。興味の対象は、派生クラスでの同名メンバ関数の定義が、オーバーライドなのかオーバーロード（継承関係をまたいだ…）なのか、それらのコールはどんなふうになされるのか…というごく初歩的なところです。あしからず。







Java class B { int i; B( int i) { this .i = i; }; public boolean eq(B o) { System.out.print( "B#eq(B) " ); return i == o.i; } } class D extends B { boolean b; D( int i, boolean b) { super (i); this .b = b; }; public boolean eq(D o) { System.out.print( "D#eq(D) " ); return super .eq(o) && b == o.b; } public boolean eq(B o) { System.out.print( "D#eq(B) " ); D dp; if ( super .eq(o)) { try { dp = (D)o; return b == dp.b; } catch (Exception e) { } } return false ; } } public class Main { public static void main(String[] args) { B b1 = new B( 1 ); B b2 = new B( 2 ); D d1 = new D( 1 , true ); D d2 = new D( 1 , false ); B bd1 = d1; System.out.println( b1.eq(b1) ); System.out.println( b1.eq(b2) ); System.out.println( d1.eq(d1) ); System.out.println( d1.eq(d2) ); System.out.println( b1.eq(d1) ); System.out.println( b1.eq(d2) ); System.out.println( d1.eq(b1) ); System.out.println( d1.eq(b2) ); System.out.println( b1.eq(bd1) ); System.out.println( b2.eq(bd1) ); System.out.println( d1.eq(bd1) ); System.out.println( d2.eq(bd1) ); System.out.println( bd1.eq(b1) ); System.out.println( bd1.eq(b2) ); System.out.println( bd1.eq(d1) ); System.out.println( bd1.eq(d2) ); } }

手始めは Java 。ルールは“危惧”していた（理解できるのだろうか…とｗ）ほど入り組んだものではなくシンプルで、継承ツリー内で同名のメンバ関数は原則としてオーバーロード、引数の型が基底クラスのものと一致するなど限られた状況でのみオーバーライド…と見なされるようです（超いいかげんな理解ｗ）。とはいえ、オーバーロードのない Smalltalk で、関数名（セレクタ）だけでオーバーライドを判断している自分には、頭のスイッチの切換えが必要でした。 これと関係するような関係しないようなところで、bd1.eq(d1) や bd1.eq(d2) で、引数のオブジェクト型 D が一致する D#eq(D) ではなく、D#eq(B) がコールされるところも、最初、理解に苦しみました。D#eq(B) が定義されていないときに、 D#eq(D) ではなく B#eq(B) がコールされるところを見るまでは…（分かってしまえば当たり前のことなんですが(^_^;)）。







C++ #include <iostream> using namespace std; class B { protected : int i; public : B( int i) : i(i) { }; virtual ~B() { }; virtual bool operator ==( const B& o) { cout << "B::==(B&) " ; return i == o.i; } }; class D : public B { protected : bool b; public : D( int i, bool b) : B(i), b(b) { }; virtual ~D() { }; virtual bool operator ==( const D& o) { cout << "D::==(D&) " ; return B:: operator ==(o) && b == o.b; } virtual bool operator ==( const B& o) { cout << "D::==(B&) " ; if (B:: operator ==(o)) { const D* dp = dynamic_cast < const D*>(&o); if (dp) return b == dp->b; } return false ; } }; int main() { B b1( 1 ), b2( 2 ); D d1( 1 , true ), d2( 1 , false ); B* bd1p = &d1; cout << ( b1 == b1 ) << endl; cout << ( b1 == b2 ) << endl; cout << ( d1 == d1 ) << endl; cout << ( d1 == d2 ) << endl; cout << ( b1 == d1 ) << endl; cout << ( b1 == d2 ) << endl; cout << ( d1 == b1 ) << endl; cout << ( d1 == b2 ) << endl; cout << ( b1 == (*bd1p) ) << endl; cout << ( b2 == (*bd1p) ) << endl; cout << ( d1 == (*bd1p) ) << endl; cout << ( d2 == (*bd1p) ) << endl; cout << ( (*bd1p) == b1 ) << endl; cout << ( (*bd1p) == b2 ) << endl; cout << ( (*bd1p) == d1 ) << endl; cout << ( (*bd1p) == d2 ) << endl; return 0 ; }

C++ では、Java のルールに加えて“隠蔽”が絡んできます。派生クラスのメンバが、基底クラス中の同じ名前を隠してしまうという C++ の古くからの“問題”（ストラウストラップ言うところの…）です。このため、D::operator==(D&) が定義された場合、D のインスタンスからは B::operator==(B&) が見えなくなり、Java ではコール可能ないくつかの組み合わせがコンパイルエラーになります。バカ往くを読んでいなかったら、ワケが分からず面食らったことでしょう。 バカが征く - うわ、これでコンパイル・エラーになるのか。

Java と同じ振る舞いをさせるには、素直に派生クラスでも D::operator==(B&) をオーバーライドするか、using B::operator==; と宣言する必要があるようです。 ときどきの雑記帖 リターンズ - それはC++の暗黒面(のひとつ)です

odz buffer - オーバーロードされた関数のオーバーライド

Eiffel class B create b_make feature i: INTEGER ; b_make(new_i: INTEGER ) is do i := new_i end ; eq(o: like Current ): BOOLEAN is do io.put_string( "{B}.eq " ); Result := (o.i = i) end ; end class D inherit B redefine eq create d_make feature b: BOOLEAN ; d_make(new_i: INTEGER ; new_b: BOOLEAN ) is do i := new_i; b := new_b end ; eq(o: like Current ): BOOLEAN is do io.put_string( "{D}.eq " ); Result := Precursor (o) and (o.b = b) end ; end class MAIN create make feature make is local b1, b2, bd1: B ; d1, d2: D do create b1.b_make( 1 ); create b2.b_make( 2 ); create d1.d_make( 1 , True ); create d2.d_make( 1 , False ); bd1 := d1; io.put_boolean( b1.eq(b1) ); io.put_new_line; io.put_boolean( b1.eq(b2) ); io.put_new_line; io.put_boolean( d1.eq(d1) ); io.put_new_line; io.put_boolean( d1.eq(d2) ); io.put_new_line; io.put_boolean( b1.eq(d1) ); io.put_new_line; io.put_boolean( b1.eq(d2) ); io.put_new_line; io.put_boolean( b1.eq(bd1) ); io.put_new_line; io.put_boolean( b2.eq(bd1) ); io.put_new_line; io.put_boolean( bd1.eq(d1) ); io.put_new_line; io.put_boolean( bd1.eq(d2) ); io.put_new_line; end ; end

Eiffel にはオーバーロードがないので、基底クラス B の仮想関数（Eifflel では「ルーチン」…）と同名のメンバ関数が派生クラス D で定義された場合、原則としてオーバーライド扱いになります（ある意味、Smalltalk 的なお気軽さ）。というか、手続き(?)上、オーバーライドを明示的に（redefine 宣言）しないかぎり、派生クラスに同名のメンバ関数を存在させることができません。また、隠蔽された基底クラスの同名メンバ関数を派生クラスのインスタンスからのコールしたい場合は、別名として宣言（rename 元の名前 as 別名）しておきます。Ruby で super が使えないときの方法（alias を使う）と発想が似ていますね。 …と書いたのを読んでいて気がついたのですが、Ruby の super が Smalltalk や Java の super とは違う、ちょっと変わった仕様になっているのは、名前はともかく、機能面でのルーツが、この Eiffel にある Precursor() だったからなのかも。…と書いたのを読んでいたら、Ruby に Eiffel 以上に大きな影響を与えている CLOS の存在をすっかり忘れていることに気が付きました(^_^;)。そう。call-next-method 。これだ。 Precursor() からだと引数を省略できる、という発想には至りにくいし…。ということでググると、そのものズバリのご本人の言及が見つかりました。→[ruby-list: 3209]

閑話休題。

クックが指摘（"A Proposal for Making Eiffel Type-safe" [CookEiffel89.pdf]）しているように、基底クラス型の変数に関連づけされた派生クラスのインスタンスを介して eq(like Current) をコールするような場合、コンパイラは {B}.eq(B) と解釈して通すのに、ランタイム時には {D}.eq(D) をコールして失敗する型システムの“穴”も見受けられます（もっとも、ここのはその穴を突くための意地悪な例なわけですが…）。 Niftyの過去ログ集 - 共変性について

今は、Eiffel もクックの頃とは違って進化し、再三指摘され続けたこの種の問題に対処可能なようになっています。たとえば、引数を共変させずに次のように書けば、ランタイムエラーを回避しながら Java や C++ と同じような振る舞いをさせられます。 class B create b_make feature i: INTEGER ; b_make(new_i: INTEGER ) is do i := new_i; end ; eq(o: B ): BOOLEAN is do io.put_string( "{B}.eq " ); Result := (o.i = i); end ; end class D inherit B redefine eq create d_make feature b: BOOLEAN ; d_make(new_i: INTEGER ; new_b: BOOLEAN ) is do i := new_i; b := new_b end ; eq(o: B ): BOOLEAN is local d: D ; do io.put_string( "{D}.eq " ); if Precursor (o) and d ?:= o then d ::= o; Result := (d.b = b); else Result := False ; end ; end ; end class MAIN create make feature make is local b1, b2, bd1: B ; d1, d2: D ; do create b1.b_make( 1 ); create b2.b_make( 2 ); create d1.d_make( 1 , True ); create d2.d_make( 1 , False ); bd1 := d1; io.put_boolean( b1.eq(b1) ); io.put_new_line; io.put_boolean( b1.eq(b2) ); io.put_new_line; io.put_boolean( d1.eq(d1) ); io.put_new_line; io.put_boolean( d1.eq(d2) ); io.put_new_line; io.put_boolean( b1.eq(d1) ); io.put_new_line; io.put_boolean( b1.eq(d2) ); io.put_new_line; io.put_boolean( d1.eq(b1) ); io.put_new_line; io.put_boolean( d1.eq(b1) ); io.put_new_line; io.put_boolean( b1.eq(bd1) ); io.put_new_line; io.put_boolean( b2.eq(bd1) ); io.put_new_line; io.put_boolean( d1.eq(bd1) ); io.put_new_line; io.put_boolean( d2.eq(bd1) ); io.put_new_line; io.put_boolean( bd1.eq(b1) ); io.put_new_line; io.put_boolean( bd1.eq(b2) ); io.put_new_line; io.put_boolean( bd1.eq(d1) ); io.put_new_line; io.put_boolean( bd1.eq(d2) ); io.put_new_line; end ; end