先ほど「速度のある点」MovingPointを定義したが,同じように 全体が等速運動する線MovingLineを定義してみよう.MovingPoint と同じように,MovingLineはLineの派生クラスにするのが自然だ.
class MovingLine : public Line {
private:
vector2D velocity;
public:
....
void move() {
move_to(p1.x+velocity.x, p1.y+verocity.y);
}
};
この定義をよく見ると,MovingPointの定義とほとんど同じ
だ.
こうなってしまうのは,図形の形(点,線,etc)と,それがどう動くか
という性質が独立だからだ.そこで,こんな風にすると,よりシンプルな
記述ができるようになる.
class GraphicObject {
....
virtual void move_to(int,int) = 0; // 絶対移動
virtual void move_rel(int,int) = 0; // 相対移動
};
class Point : public GraphicObject {
....
void move_to(int,int);
void move_rel(int,int);
....
};
class Line : public GraphicObject {
....
void move_to(int,int);
void move_rel(int,int);
....
}
class MovingObject {
protected:
vector2D velocity;
public:
....
virtual void move_rel(int,int) = 0;
void move() {
move_rel(velocity.x, verocity.y);
}
};
class MovingPoint: public Point, public MovingObject {
public:
void move_rel(int x, int y) { Point::move_rel(x,y); }
....
};
class MovingLine: public Line, public MovingObject {
public:
void move_rel(int x, int y) { Point::move_rel(x,y); }
....
};
この例では,「速度のある物体」を表す抽象クラス MovingObjectを定義して, これとPointの両方を継承したものをMovingPointとし, MovingObjectとLineを継承したものがMovingLine というふうにしている.図形の形と,その運動の性質を別々なクラス として実現しているわけだ.このときのクラスの継承関係は, こんな感じになる.
この例でちょっと気になるのが,相対移動をする関数move_rel()
の扱いだ.MovingPoint::move_rel()を見てみると,
Point::move_rel()をそのまま利用している.しかし,
派生クラスでは基底クラスのメンバ関数がそのまま使えるのでは
なかったのか?
こんなことをしている理由は,MovingObjectの中で
move_rel()を純粋仮想関数として定義しているためだ.
純粋仮想関数は,それを定義しているクラスの派生クラスの中で
定義しないと,未定義とみなされてしまうのだ.実際のmove_rel()
が定義されているのはPointやLineの中であり,
これらはMovingObjectの派生クラスではないので,
そのままではmove_rel()が未定義になってしまう.そこで,
MovingPointの中でmove_rel()を再定義していると
いうわけだ.
しかし,毎回これではちょっと賢さがいまいちな気がする.C++では, こんな場合の書き方もちゃんと存在する.
class GraphicObject {
....
virtual void move_to(int,int) = 0; // 絶対移動
virtual void move_rel(int,int) = 0; // 相対移動
};
class Point : public virtual GraphicObject {
....
void move_to(int,int);
void move_rel(int,int);
....
};
class Line : public virtual GraphicObject {
....
void move_to(int,int);
void move_rel(int,int);
....
}
class MovingObject : public virtual GraphicObject {
protected:
vector2D velocity;
public:
....
void move() {
move_rel(velocity.x, verocity.y);
}
};
class MovingPoint: public Point, public MovingObject {
....
};
class MovingLine: public Line, public MovingObject {
....
};
Point, Line, MovingObjectをGraphicObjectの
派生クラスとして定義しているが,このときにpublic virtual
という宣言をしているところがミソだ.基底クラスの宣言にvirtual
をつけることで,継承関係を
こんな形にすることができる.
こうすると,MovingPointやMovingLineで
move_rel()を再定義しなくても,Point::move_rel()
の定義を使ってちゃんと動いてくれる.
ちなみにvirtualをつけないで継承すると,こういう継承関係になる.
あんまり違わない気がするが,virtualでない継承関係の場合, MovingObjectの基底のGraphicObjectで定義された 純粋仮想関数が,GraphicObject→MovingObject→MovingPoint の継承関係の中で定義されていないので,エラーになってしまう. virtualをつけた方だと,それらの純粋仮想観数はPoint の中で定義されているので,エラーにならない.
継承をvirtualにするかどうかは,そのクラスが後で多重継承に使われる
かどうかを考えて決めなくてはならない.これはC++のいやらしい点の一つ
である.