先ほど「速度のある点」MovingPointを定義したが,同じように 全体が等速運動する線MovingLineを定義してみよう.MovingPoint と同じように,MovingLineはLineの派生クラスにするのが自然だ.
この定義をよく見ると,MovingPointの定義とほとんど同じ だ. こうなってしまうのは,図形の形(点,線,etc)と,それがどう動くか という性質が独立だからだ.そこで,こんな風にすると,よりシンプルな 記述ができるようになる.class MovingLine : public Line { private: vector2D velocity; public: .... void move() { move_to(p1.x+velocity.x, p1.y+verocity.y); } };
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++では, こんな場合の書き方もちゃんと存在する.
Point, Line, MovingObjectをGraphicObjectの 派生クラスとして定義しているが,このときにpublic virtual という宣言をしているところがミソだ.基底クラスの宣言にvirtual をつけることで,継承関係を こんな形にすることができる.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 { .... };
こうすると,MovingPointやMovingLineで
move_rel()
を再定義しなくても,Point::move_rel()
の定義を使ってちゃんと動いてくれる.
ちなみにvirtualをつけないで継承すると,こういう継承関係になる.
あんまり違わない気がするが,virtualでない継承関係の場合, MovingObjectの基底のGraphicObjectで定義された 純粋仮想関数が,GraphicObject→MovingObject→MovingPoint の継承関係の中で定義されていないので,エラーになってしまう. virtualをつけた方だと,それらの純粋仮想観数はPoint の中で定義されているので,エラーにならない.
継承をvirtualにするかどうかは,そのクラスが後で多重継承に使われる
かどうかを考えて決めなくてはならない.これはC++のいやらしい点の一つ
である.