next up previous
Next: 抽象クラス Up: オブジェクト指向っぽいC++ Previous: 継承(インヘリタンス)

仮想関数

では,点を動かす関数を定義してみよう.まず,Point を指定位置に移動させるメンバ関数.

class Point {
  protected:
     ....
  public:
     ....
    void move_to(int x, int y) {
        draw_point(coord.x,coord,y,0);
        coord.x = x;
        coord.y = y;
        draw_point(x,y,color);
    }
}:

次に,速度に応じてMovingPointを動かす関数を定義してみよう.

class MovingPoint : public Point {
     ....
    void move() {
        move_to(coord.x+velocity.x, coord.y+verocity.y);
    }
};

これを定義しておけば,たくさんの点をいっぺんに動かすためには, MovingPointの配列を使って次のように書けばよい.

MovingPoint *pts[N];
  ...
for (int i = 0; i < N; i++)
    pts[i]->move();

話は変わって,今度は加速度を持った点AccelPointを定義してみよう. 加速度を持つ点は当然速度も持つから,AccelPointMovingPoint の派生クラスにするのはごく自然であろう.ついでに,AccelPoint が動くための関数も作ってみよう.

class AccelPoint : public MovingPoint {
  private:
    vector2D accel;
     ....
  public:
     ....
    void move() {
        MovingPoint::move();
        velocity = velocity+accel;
    }
};
AccelPoint::move()の中では,基底クラスのメンバ関数 MovingPoint::move()を直接呼び出している.また, vector2Dの加算を使っている.

さて,ではこんなことをしたらどうなるだろうか?

MovingPoint* pts[N];
int i;
for (i = 0; i < N; i++) {
    if (i % 2 == 0)  pts[i] = new MovingPoint(...);
    else             pts[i] = new AccelPoint(...);
}

for(;;) {
    for (i = 0; i < N; i++)
        pts[i]->move();
}

MovingPointへのポインタであるpts[i]に,その派生クラス であるAccelPointへのポインタを代入しても文句を言われない というのがこの話のポイントだ.AccelPointMovingPoint を継承しているから, AccelPointのインスタンスに対しては, MovingPointの持つメンバ関数を適用しても大丈夫なはずである. それでは,AccelPointで再定義されているメンバ関数move() を呼んだらどうなるか?pts[1]にはAccelPointのインスタンスへの ポインタが入っているはずなので,pts[1]->move()AccelPoint::move()が呼ばれるのか?それともMovingPoint::move() が呼ばれるのか?

答えは,後者である.pts[1]はあくまでMovingPointへの ポインタだから,それに対して適用されるmove()MovingPoint::move() になる.しかし,ここでもしAccelPoint::move()が呼ばれるなら, 一様な関数呼び出しに対して,ある者は等速運動をし,またある者は 等加速度運動をすることになる.どう動くかはインスタンス自身が知っている というわけだ.

仮想関数を使うと,こういう振る舞いをさせることができる.MovingPoint の定義の中で,

class MovingPoint : public Point {
     ....
    virtual void move() {
        move_to(coord.x+velocity.x, coord.y+verocity.y);
    }
};
という指定をすると,MovingPointへのポインタの指すインスタンスに対して メンバ関数move()を呼んだとき,そのインスタンスが属するクラスの move()が呼ばれる.

なお,仮想関数を持つクラスを定義するときは,そのデストラクタの宣言にも virtualをつけなくてはならない.



Akinori ITO
Thu Aug 7 15:05:13 JST 1997