ダブルディスパッチダブルディスパッチ(英: double dispatch)は、多重ディスパッチのひとつの形態で、2個のオブジェクトから、それに対応する実際の手続きが実行時に決まる、というものである。近年のオブジェクト指向プログラミング言語でよく見られる obj.methodName(arg, ...) というような構文では、obj に対応する1個のオブジェクトから、実行されるメソッドが決定される「シングルディスパッチ」であるわけだが、それに対して複数個のオブジェクトが関与して、多重定義されたメソッドなどから、実行される一つが決定されるのが多重ディスパッチで、多重ディスパッチに関与するオブジェクトを2個に限定したものがダブルディスパッチである。また、シングルディスパッチの言語における複数のクラス間で同様のことを実現するイディオムを指して言う場合もある。[1] 例たとえば、以下のような状況でダブルディスパッチを活用することができる。
コスト一般にメソッドディスパッチとは、引数の動的な型に応じて適切な手続きを選択して呼び出すことであり、オブジェクト指向言語の実行時におけるオーバヘッドとして重要な位置を占める。シングルディスパッチで、さらに多重継承などが無ければ、テーブルのオフセットをコンパイル時に静的に決定することなどもできるが、ダブルディスパッチでは組み合わせの数も多く、動的なディスパッチが必要になるなど、シングルディスパッチに比べコストは大きい。 代替手法前述のように二項演算子という、(LispやForthなどを除いた)多くのプログラミング言語で好まれている機能において望まれるものであるため、シングルディスパッチのみがあるオブジェクト指向プログラミング言語でダブルディスパッチのようなふるまいを実現する手法が考えられている。ここでは一例としてRubyのものを示す。 たとえばRubyに複素数クラスを自作して追加したいとする[2]。Rubyでは二項演算子 z1 = Complex.new(1.0, 0.0)
z2 = z1 + 2.0
これに対し、次のようにも書きたいわけだが、 z3 = Complex.new(0.0, 1.0)
z4 = 3.0 + z3
もし何も仕掛けが無ければ、あらゆる既存の数値クラスについて、「複素数を引数にした場合」を追加する必要があり現実的ではない。しかし、Rubyにおける数値関係のクラスの、演算子に対応するメソッドは次のようにふるまうようになっていて、 class Num
def +(other)
if otherは既知のオブジェクト then
return 結果 # 結果を計算して返す
else
left, right = other.coerce(self)
return left + right # coerceの結果により計算する
end
end
end
追加したいクラス(たとえばここでは複素数クラス)に ダブルディスパッチはメソッドのオーバーロード以上である一見したところでは、ダブルディスパッチはメソッドのオーバーロードの自然な結果である。メソッドのオーバーロードは呼び出されるクラスだけではなく、引数の型にも応じて呼び出しが行われるようにすることができるが、オーバーロードされたメソッドの呼び出しはほぼ一つの仮想関数テーブルを通じて行われるため、動的なディスパッチは呼び出すオブジェクトの種類によってのみ決まる。下記のC++の例において、あるゲームで衝突の判定を行う場合を考える。 // SpaceShip側
class SpaceShip {};
class GiantSpaceShip : public SpaceShip {};
// Asteroid側
class Asteroid {
public:
virtual void CollideWith(SpaceShip&) {
cout << "Asteroid hit a SpaceShip" << endl;
}
virtual void CollideWith(GiantSpaceShip&) {
cout << "Asteroid hit a GiantSpaceShip" << endl;
}
};
class ExplodingAsteroid : public Asteroid {
public:
virtual void CollideWith(SpaceShip&) {
cout << "ExplodingAsteroid hit a SpaceShip" << endl;
}
virtual void CollideWith(GiantSpaceShip&) {
cout << "ExplodingAsteroid hit a GiantSpaceShip" << endl;
}
};
ここで、 Asteroid theAsteroid;
SpaceShip theSpaceShip;
GiantSpaceShip theGiantSpaceShip;
があるとすると、下記のように処理できる。 // Asteroid側、SpaceShip側 共に静的
theAsteroid.CollideWith(theSpaceShip);
theAsteroid.CollideWith(theGiantSpaceShip);
上記のコードは動的なディスパッチを使わず、静的なディスパッチにより
さらに、 // Asteroid側、SpaceShip側 共に静的
ExplodingAsteroid theExplodingAsteroid;
theExplodingAsteroid.CollideWith(theSpaceShip);
theExplodingAsteroid.CollideWith(theGiantSpaceShip);
上記のコードは
がここでtheExplodingAsteroidを、 // Asteroid側は動的、SpaceShip側は静的
Asteroid& theAsteroidReference = theExplodingAsteroid;
theAsteroidReference.CollideWith(theSpaceShip);
theAsteroidReference.CollideWith(theGiantSpaceShip);
動的な(シングル)ディスパッチが起きた結果 Asteroid のメソッドでなく、ExplodingAsteroid のメソッドが呼ばれ、
しかし SpaceShip& theSpaceShipReference = theGiantSpaceShip;
theAsteroid.CollideWith(theSpaceShipReference); // Asteroid側は静的、SpaceShip側は動的
theAsteroidReference.CollideWith(theSpaceShipReference); // Asteroid側は動的、SpaceShip側は動的
という処理では、 原因は、Asteroid側の動的なシングルディスパッチしか実現できておらず、SpaceShip側も含めた動的なダブルディスパッチができていない事である。 C++ におけるダブルディスパッチ上述の問題は、Visitor パターンで用いられているものと同様の手法で解決できる。 virtual void CollideWith(Asteroid& inAsteroid) {
inAsteroid.CollideWith(*this);
}
を持っているとすると、先ほどの例ではうまく動作しなかったが、以下の例はうまく動作する。 SpaceShip& theSpaceShipReference = theGiantSpaceShip;
Asteroid& theAsteroidReference = theExplodingAsteroid;
theSpaceShipReference.CollideWith(theAsteroid);
theSpaceShipReference.CollideWith(theAsteroidReference);
この例は、期待通りに
注
関連項目 |
Portal di Ensiklopedia Dunia