メソッドのオーバーライド
問題
次は、ちょっと難しくなります。6種類の言語を使って「親クラス」と「子クラス」を定義して、メソッドのオーバーライドの処理を書いてみました。同じような動作を期待したのですが、1つだけ出力結果が違うものがありました。それは、どの言語でしょうか?
program Project1; {$APPTYPE CONSOLE} type TParent = class public constructor Create; protected procedure foo; virtual; end; TChild = class(TParent) public constructor Create; protected procedure foo; override; end; constructor TParent.Create; begin Self.foo; end; procedure TParent.foo; begin Writeln(’TParent#foo’); end; constructor TChild.Create; begin inherited; end; procedure TChild.foo; begin inherited; Writeln(’TChild#foo’); end; begin TChild.Create; end.
#include <iostream> using namespace std; class TParent { public: TParent(){ this->foo(); } protected: virtual void foo() { cout << "TParent#foo" << endl; } }; class TChild : public TParent { public: TChild() : TParent(){ } protected: virtual void foo() { TParent::foo(); cout << "TChild#foo" << endl; } }; int main(int argc, char* argv[]) { new TChild(); return 0; }
package project1; class TParent { public TParent() { this.foo(); } protected void foo() { System.out.println("TParent#foo"); } } class TChild extends TParent { public TChild() { } protected void foo() { super.foo(); System.out.println("TChild#foo"); } } public class Project1 { public static void main(String[] args) { new TChild(); } }
namespace project1 { class TParent { public TParent() { this.foo(); } protected virtual void foo() { System.Console.WriteLine("TParent#foo"); } } class TChild : TParent { public TChild() : base() { } protected override void foo() { base.foo(); System.Console.WriteLine("TChild#foo"); } } public class Project1 { public static void Main() { new TChild(); } } }
<?php class TParent { public function __construct() { $this->foo(); } protected function foo() { echo "TParent#foo\n"; } } class TChild extends TParent { public function __construct() { parent::__construct(); } protected function foo() { parent::foo(); echo "TChild#foo\n"; } } new TChild(); ?>
class TParent def initialize foo end private def foo puts "TParent#foo" end end class TChild < TParent def initialize super end private def foo super puts "TChild#foo" end end TChild.new
解説
正解は「C++」。
C++以外の言語では期待した、
TParent#foo TChild#foo
という出力が得られますが、C++では単に「TParent#foo」とだけ出力されます。問題のコードはメソッドのオーバーライドの機能を使用して、以下のような順番で処理が進むことを期待しています。
- TChildクラスのインスタンスを生成
- TChildクラスのコンストラクタが呼び出される
- TParentクラスのコンストラクタが呼び出される
- TParentクラスのコンストラクタ内でfooメソッドを呼び出す
- TChildクラスのfooメソッドが呼び出される
- TChildクラスのfooメソッドがTParentクラスのfooメソッドを呼び出す
もちろん一般的には、コンストラクタの処理中は、派生クラスでオーバーライドされる可能性のあるメソッドを呼び出すべきできはありません。構築途中のインスタンスが外部に漏れる危険性があるからです。Delphi/Java/C#/PHP/Rubyでは、TParentクラスのコンストラクタの処理中にTChildクラスのfooメソッドが呼び出されてしまいます。
しかしC++では「コンストラクタの中から自身の仮想メソッドを呼び出してたとしても、派生クラスのメソッドではなく、自身のメソッドが呼び出される」という仕様になっています。このため、TChildクラスのfooメソッドは呼び出されることなく「TParent#foo」とだけ出力されたのです。
なお、Rubyに関しては特徴的な動作があります。問題のコードではTParentクラスのfooメソッドは「private」として修飾され、一見すると派生クラスTChildではオーバーライドされないように思えます。しかし実際にはオーバーライドが行われてしまいます。この点、特に注意が必要でしょう。