コンストラクタの連鎖とデフォルト引数
問題
最後はちょっと難しくなります。クラス継承時のコンストラクタの連鎖と、メソッドのデフォルト引数について。これも6つの言語でテストコードを書いてみました。言語の中には、デフォルト引数の機能を持たないものもありますので、それらもできるだけ似せて書いたつもりです。でも、やはり1つだけ結果の異なるものがありました。それはどの言語でしょうか?
program Project1; {$APPTYPE CONSOLE} type TParent = class public constructor Create(x: Integer = 1); end; TChild = class(TParent) public constructor Create(x: Integer); end; constructor TParent.Create(x: Integer); begin Write(x); end; constructor TChild.Create(x: Integer); begin inherited Create; Write(x); end; begin TChild.Create(2); end.
#include <iostream> using namespace std; class TParent { public: TParent(int x = 1) { cout << x; } }; class TChild : public TParent { public: TChild(int x) : TParent() { cout << x; } }; int main(int argc, char* argv[]) { new TChild(2); return 0; }
package project1; class TParent { public TParent() { this(1); } public TParent(int x) { System.out.print(x); } } class TChild extends TParent { public TChild(int x) { System.out.print(x); } } public class Project1 { public static void main(String[] args) { new TChild(2); } }
namespace project1 { class TParent { public TParent() : this(1) { } public TParent(int x) { System.Console.Write(x); } } class TChild : TParent { public TChild(int x) : base() { System.Console.Write(x); } } public class Project1 { public static void Main() { new TChild(2); } } }
<?php class TParent { public function __construct($x = 1) { echo $x; } } class TChild extends TParent { public function __construct($x) { parent::__construct(); echo $x; } } new TChild(2); ?>
class TParent def initialize(x = 1) print x end end class TChild < TParent def initialize(x) super print x end end TChild.new(2)
解説
正解は「Ruby」。
Ruby以外の言語では、期待した「12」という出力が得られますが、Rubyでは「22」と出力されます。問題のコードは、「子クラス」のコンストラクタが「直接の親クラス」のコンストラクタを呼び出す処理が、以下のような順番で進むことを期待しています。
- TChildクラスのインスタンスを生成
- TChildクラスのコンストラクタに「2」が渡される
- TChildクラスのコンストラクタがTParentクラスのデフォルトコンストラクタを呼び出す
- TParentクラスのデフォルトコンストラクタは「1」を受け取る
しかし実際には、RubyだけがTParentクラスのデフォルトコンストラクタに「2」を受け取ってしまっています。どこに問題が隠れているのでしょうか? 実はRubyでは、親クラスのメソッド(コンストラクタを含む)を呼び出す際、括弧と引数が省略された「super」と記述すると、子クラス自身が引数として受け取った変数が自動的に親クラスのメソッドに渡されます。つまり、問題のコードは以下のように記述したのと同じになります。
class TChild < TParent def initialize(x) super(x) # superと同じ print x end end
もちろん、以下のように括弧「()」を明示的に記述することにより、他の言語と同じように動作させることもできます。
class TChild < TParent def initialize(x) super() # super()はsuper(1)と同じ print x end end
なお、Delphiの「inherited」にはRubyと同じ特徴があります。問題のコードは、実際には以下のコードと同じになります。
constructor TChild.Create(x: Integer); begin inherited Create(1); // inherited Create; と同じ Write(x); end;
Rubyと同様に「inherited;」とだけ記述すると、子クラス自身が引数として受け取った変数が自動的に親クラスのメソッドに渡されます。
constructor TChild.Create(x: Integer); begin inherited; // inherited Create(x); と同じ Write(x); end;