では,点を動かす関数を定義してみよう.まず,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 が動くための関数も作ってみよう.
AccelPoint::move()の中では,基底クラスのメンバ関数 MovingPoint::move()を直接呼び出している.また, vector2Dの加算を使っている.class AccelPoint : public MovingPoint { private: vector2D accel; .... public: .... void move() { MovingPoint::move(); velocity = velocity+accel; } };
さて,ではこんなことをしたらどうなるだろうか?
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 の定義の中で,
という指定をすると,MovingPointへのポインタの指すインスタンスに対して メンバ関数move()を呼んだとき,そのインスタンスが属するクラスの move()が呼ばれる.class MovingPoint : public Point { .... virtual void move() { move_to(coord.x+velocity.x, coord.y+verocity.y); } };
なお,仮想関数を持つクラスを定義するときは,そのデストラクタの宣言にも virtualをつけなくてはならない.