ダブルディスパッチ

ダブルディスパッチ（英: double dispatch）は、多重ディスパッチのひとつの形態で、2個のオブジェクトから、それに対応する実際の手続きが決まる、というものである。近年のオブジェクト指向プログラミング言語でよく見られる obj.methodName(arg, ...) というような構文では、obj に対応する1個のオブジェクトから、実行されるメソッドが決定される「シングルディスパッチ」であるわけだが、それに対して複数個のオブジェクトが関与して、多重定義されたメソッドなどから、実行される一つが決定されるのが多重ディスパッチで、多重ディスパッチに関与するオブジェクトを2個に限定したものがダブルディスパッチである。また、シングルディスパッチの言語における複数のクラス間で同様のことを実現するイディオムを指して言う場合もある。

コスト

一般にメソッドディスパッチとは、引数の動的な型に応じて適切な手続きを選択して呼び出すことであり、オブジェクト指向言語の実行時におけるオーバヘッドとして重要な位置を占める。シングルディスパッチで、さらに多重継承などが無ければ、テーブルのオフセットをコンパイル時に静的に決定することなどもできるが、ダブルディスパッチでは組み合わせの数も多く、動的なディスパッチが必要になるなど、シングルディスパッチに比べコストは大きい。

代替手法

前述のように二項演算子という、（LispやForthなどを除いた）多くのプログラミング言語で好まれている機能において望まれるものであるため、シングルディスパッチのみがあるオブジェクト指向プログラミング言語でダブルディスパッチのようなふるまいを実現する手法が考えられている。ここでは一例としてRubyのものを示す。
たとえばRubyに複素数クラスを自作して追加したいとする。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
  endend
追加したいクラス（たとえばここでは複素数クラス）に coerce というメソッドを一つ定義し、適切な値を返すようにすれば、任意の演算子に対して望んだような結果にできる。

ダブルディスパッチは関数のオーバーロード以上である

一見したところでは、ダブルディスパッチは関数のオーバーロードの自然な結果である。関数のオーバーロードは呼び出されるクラスだけではなく、引数の型にも応じて呼び出しが行われるようにすることができるが、オーバーロードされた関数の呼び出しはほぼ一つの仮想関数テーブルを通じて行われるため、動的なディスパッチは呼び出すオブジェクトの種類によってのみ決まる。下記の例において、あるゲームで衝突の判定を行う場合を考える。
class SpaceShip {};class GiantSpaceShip : public SpaceShip {};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;
があるとすると、関数のオーバーロードのために
theAsteroid.CollideWith(theSpaceShip); 
theAsteroid.CollideWith(theGiantSpaceShip);
上記のコードは、動的なディスパッチを使用せず、 Asteroid hit a SpaceShip および Asteroid hit a GiantSpaceShip とそれぞれ表示する。
さらに、
ExplodingAsteroid theExplodingAsteroid;theExplodingAsteroid.CollideWith(theSpaceShip); 
theExplodingAsteroid.CollideWith(theGiantSpaceShip);
上記のコードはExplodingAsteroid hit a SpaceShip およびExplodingAsteroid hit a GiantSpaceShip と、やはり動的なディスパッチを使用せずに表示する。
Asteroidに対する参照を使って動的なディパッチを用いると、
Asteroid& theAsteroidReference = theExplodingAsteroid;theAsteroidReference.CollideWith(theSpaceShip); 
theAsteroidReference.CollideWith(theGiantSpaceShip);
ExplodingAsteroid hit a SpaceShip および ExplodingAsteroid hit a GiantSpaceShipと期待通りに表示する。
しかし、
SpaceShip& theSpaceShipReference = theGiantSpaceShip;theAsteroid.CollideWith(theSpaceShipReference); 
theAsteroidReference.CollideWith(theSpaceShipReference);
は、Asteroid hit a SpaceShip および ExplodingAsteroid hit a SpaceShipと表示するが、これはいずれも正しくない。問題は、仮想関数が C++ によって動的にディスパッチが行われるのに対して、関数のオーバーロードは静的に行われるためである。