next up previous
Next: C++のめんどくさい利用法 Up: オブジェクト指向っぽいC++ Previous: 抽象クラス

多重継承

先ほど「速度のある点」MovingPointを定義したが,同じように 全体が等速運動する線MovingLineを定義してみよう.MovingPoint と同じように,MovingLineLineの派生クラスにするのが自然だ.

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とし, MovingObjectLineを継承したものがMovingLine というふうにしている.図形の形と,その運動の性質を別々なクラス として実現しているわけだ.このときのクラスの継承関係は, こんな感じになる.

picture337

この例でちょっと気になるのが,相対移動をする関数move_rel() の扱いだ.MovingPoint::move_rel()を見てみると, Point::move_rel()をそのまま利用している.しかし, 派生クラスでは基底クラスのメンバ関数がそのまま使えるのでは なかったのか?

こんなことをしている理由は,MovingObjectの中で move_rel()を純粋仮想関数として定義しているためだ. 純粋仮想関数は,それを定義しているクラスの派生クラスの中で 定義しないと,未定義とみなされてしまうのだ.実際のmove_rel() が定義されているのはPointLineの中であり, これらは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, MovingObjectGraphicObjectの 派生クラスとして定義しているが,このときにpublic virtual という宣言をしているところがミソだ.基底クラスの宣言にvirtual をつけることで,継承関係を こんな形にすることができる.

picture356

こうすると,MovingPointMovingLinemove_rel()を再定義しなくても,Point::move_rel() の定義を使ってちゃんと動いてくれる. ちなみにvirtualをつけないで継承すると,こういう継承関係になる.

picture367

あんまり違わない気がするが,virtualでない継承関係の場合, MovingObjectの基底のGraphicObjectで定義された 純粋仮想関数が,GraphicObject→MovingObject→MovingPoint の継承関係の中で定義されていないので,エラーになってしまう. virtualをつけた方だと,それらの純粋仮想観数はPoint の中で定義されているので,エラーにならない.

継承をvirtualにするかどうかは,そのクラスが後で多重継承に使われる かどうかを考えて決めなくてはならない.これはC++のいやらしい点の一つ である.


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