では,点を動かす関数を定義してみよう.まず,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を定義してみよう. 加速度を持つ点は当然速度も持つから,AccelPointをMovingPoint の派生クラスにするのはごく自然であろう.ついでに,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へのポインタを代入しても文句を言われない というのがこの話のポイントだ.AccelPointはMovingPoint を継承しているから, 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をつけなくてはならない.