TENKAIKEN Storehouse

Contents ♪

C++ クラスの継承 2024.07.15
クラスの継承
Shape(図形)というスーパークラスからRect(四角)というサブクラスを派生している。Shapeそのものは具体的な図形ではなく、全ての図形が持つ起点座標(x y)をメンバに持つ。Rectは左上座標を起点をShapeクラスで管理し、幅と高さをRectクラスで記憶する。
Rectのコンストラクタは、スーパークラスのShapeのコンストラクタ初期化をメンバイニシャライザと同様に呼び出して初期化する。
Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
draw()は描画しないが描画したつもりで図形の内容を表示している。draw()はShapeとRectの両方に存在するが、Rectのインスタンスから呼び出すdraw()は、Rectクラスのdraw()の方が呼び出される。Rectのdraw()では、スーパークラスShapeのメンバ関数getx()とgety()を呼び出している。
Rectは、Shapeを継承してもShapeのprivateメンバにはアクセスできない。
#include <cstdio>

using namespace std;

class Shape {
public:
    Shape(int _x, int _y);
    int getx() { return x; };
    int gety() { return y; };
private:
    int x, y;       // 全ての図形で起点になる座標
};

class Rect : public Shape {
public:
    Rect(int _x, int _y, int _wid, int _hei);
    void draw();
private:
    int width, height;
};

Shape::Shape(int _x, int _y)
{
    x = _x;
    y = _y;
}

Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
{                       // スーパークラスのコンストラクタ呼び出し初期化
    width = _wid;
    height = _hei;
}

void Rect::draw()
{
    printf("draw Rect (%d %d)-(%d %d)\n",
        getx(), gety(), getx() + width, gety() + height);
            // Shapeのメンバ関数の呼び出し 
}

int main()
{
    Rect rect(10, 20, 30, 15);
    rect.draw();        // draw Rect (10 20)-(40 35)
    return 0;
}
C++ クラスの継承 2024.07.15
スーパークラスのメンバを継承する
Shapeクラスのmod()は、派生したサブクラスには定義されていない。Rectクラスは、Shapeのpublicメンバにアクセスできるので、RectのインスタンスのrectはShapeクラスのmod()を呼び出すことができる。
#include <cstdio>

using namespace std;

class Shape {
public:
    Shape(int _x, int _y);
    void mod(int _x, int _y);
    int getx() { return x; };
    int gety() { return y; };
private:
    int x, y;
};

class Rect : public Shape {
public:
    Rect(int _x, int _y, int _wid, int _hei);
    void draw();
private:
    int width, height;
};

Shape::Shape(int _x, int _y)
{
    x = _x;
    y = _y;
}

void Shape::mod(int _x, int _y)
{
    x = _x;
    y = _y;
}

Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
{
    width = _wid;
    height = _hei;
}

void Rect::draw()
{
    printf("draw Rect (%d %d)-(%d %d)\n",
        getx(), gety(), getx() + width, gety() + height);
}

int main()
{
    Rect rect(10, 20, 30, 15);
    rect.draw();        // draw Rect (10 20)-(40 35)
    rect.mod(15, 25);   // Shapeのメンバ関数の呼び出し
    rect.draw();        // draw Rect (15 25)-(45 40)
    return 0;
}
C++ クラスの継承 2024.07.15
再定義元のスーパークラスのメンバ関数を使う
図形を変更する関数mod()を派生したRectクラスに実装する。Rectクラスのmod()はスーパークラスShapeの再定義となる。Rectクラスのインスタンスからmod()を呼び出すと、Rectクラスのmod()が実行される。
Rectクラスのmod()の中で、スーパークラスのShapeのmod()で起点座標を初期化しているが、この場合は、スコープ解決でShapeクラスのmod()であることを明示する必要がある。RectのインスタンスからShapeクラスの方のmod()を呼び出す場合も、同様にスコープ解決を行う。
Shape()のmod()を呼び出した場合は、起点座標の変更になりrectの四角の移動になる。
#include <cstdio>

using namespace std;

class Shape {
public:
    Shape(int _x, int _y);
    void mod(int _x, int _y);
    int getx() { return x; };
    int gety() { return y; };
private:
    int x, y;
};

class Rect : public Shape {
public:
    Rect(int _x, int _y, int _wid, int _hei);
    void mod(int _x, int _y, int _wid, int _hei);
    void draw();
private:
    int width, height;
};

Shape::Shape(int _x, int _y)
{
    x = _x;
    y = _y;
}

void Shape::mod(int _x, int _y)
{
    x = _x;
    y = _y;
}

Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
{
    width = _wid;
    height = _hei;
}

void Rect::mod(int _x, int _y,  int _wid, int _hei)
{
    Shape::mod(_x, _y);
    width = _wid;
    height = _hei;
}

void Rect::draw()
{
    printf("draw Rect (%d %d)-(%d %d)\n",
        getx(), gety(), getx() + width, gety() + height);
}

int main()
{
    Rect rect(0, 0, 0, 0);
    rect.mod(50, 80, 25, 60);
    rect.draw();        // draw Rect (50 80)-(75 140)
    rect.Shape::mod(100, 120);      // スーパークラス側のmod()
    rect.draw();        // draw Rect (100 120)-(125 180)
    return 0;
}
C++ クラスの継承 2024.07.15
スーパークラスの型でメンバ関数を呼び出す
サブクラスのインスタンスをスーパークラスのインスタンスに代入すると、サブクラスが継承するスーパークラスへのメンバのコピーになる。
RectクラスのインスタンスをShapeとRectクラスのそれぞれのポインタに代入して、draw()を呼び出すと、Shapeクラスポインタからならば、継承したShepeクラスのdraw()が呼び出され、Rectクラスのポインタからは、Rectクラスのdraw()が呼び出される。インスタンスを示すポインタ型に従って、継承で再定義したメンバ関数のどちらを呼び出すかを決定する。
スーパークラスShapeのポインタからサブクラスのRectクラスのポインタへを代入するときは、明示的なキャストが必要。
#include <cstdio>

using namespace std;

class Shape {
public:
    Shape(int _x, int _y);
    int getx() { return x; };
    int gety() { return y; };
    void draw();
private:
    int x, y;
};

class Rect : public Shape {
public:
    Rect(int _x, int _y, int _wid, int _hei);
    void draw();
private:
    int width, height;
};

Shape::Shape(int _x, int _y)
{
    x = _x;
    y = _y;
}

void Shape::draw()
{
    printf("draw Shape (%d %d)\n", getx(), gety());
}

Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
{
    width = _wid;
    height = _hei;
}

void Rect::draw()
{
    printf("draw Rect (%d %d)-(%d %d)\n",
        getx(), gety(), getx() + width, gety() + height);
}

int main()
{
    Rect rect(10, 20, 30, 15);
    rect.draw();        // draw Rect (10 20)-(40 35)
    Shape shp = rect;   // Rectが継承するShapeオブジェクトがコピー
    shp.draw();         // draw Shape (10 20)

    // ポインタのクラス型により呼び出すメンバ関数が決まる
    Shape* pshp = &rect;
    Rect* prect = &rect;
    pshp->draw();       // draw Shape (10 20)
    prect->draw();      // draw Rect (10 20)-(40 35)

    // スーパークラスからサブクラスへのポインタ代入はキャストが必要
    Rect* prect2 = (Rect*)pshp;
    prect2->draw();     // draw Rect (10 20)-(40 35)
    return 0;
}
C++ クラスの継承 2024.07.15
サブクラス共通のスーパークラスのメンバ関数を呼び出す
RectクラスとCircleクラスのインスタンスを、共通のスーパークラスのShapeのポインタに代入して、スーパークラスのdraw()を呼び出している。Shapeクラスのdraw()によりそれぞれの図形の起点座標を出力している。
#include <cstdio>

using namespace std;

class Shape {
public:
    Shape(int _x, int _y);
    int getx() { return x; };
    int gety() { return y; };
    void draw();
private:
    int x, y;
};

class Rect : public Shape {
public:
    Rect(int _x, int _y, int _wid, int _hei);
    void draw();
private:
    int width, height;
};

class Circle : public Shape {
public:
    Circle(int _x, int _y, int _r);
    void draw();
private:
    int r;
};

Shape::Shape(int _x, int _y)
{
    x = _x;
    y = _y;
}

void Shape::draw()
{
    printf("draw Shape (%d %d)\n", getx(), gety());
}

Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
{
    width = _wid;
    height = _hei;
}

void Rect::draw()
{
    printf("draw Rect (%d %d)-(%d %d)\n",
        getx(), gety(), getx() + width, gety() + height);
}

Circle::Circle(int _x, int _y,  int _r) : Shape(_x, _y)
{
    r = _r;
}

void Circle::draw()
{
    printf("draw Circle C=(%d %d) r=%d\n", getx(), gety(), r);
}

int main()
{
    Rect rect(10, 20, 30, 15);
    Circle crc(100, 120, 25);
    Shape* pshp;

    pshp = &rect;
    pshp->draw();       // draw Shape (10 20)
    pshp = &crc;
    pshp->draw();       // draw Shape (100 120)
    return 0;
}
C++ クラスの継承 2024.07.15
仮想関数のサブクラスの再定義関数を呼び出す
スーパークラスのメンバ関数にvirtualを付けて仮想関数とし、仮想関数をサブクラスが再定義した場合、スーパークラスのポインタから仮想関数を呼び出すと、サブクラスで再定義した実装が呼び出される。
RectとCirclrのインスタンスをShapeクラスのポインタに代入してdraw()を呼び出すと、どちらもそれぞれのサブクラスのdraw()の実行となる。仮に、Shapeクラスのdraw()のvirtualを消した場合は、最後のRectとCircleのどちらもShapeのdraw()を呼び出す結果となる。
サブクラス側で仮想関数を再定義しないならば、常にスーパークラスの仮想関数が実行される。スコープ解決により明示的にスーパークラスの仮想関数の実装を呼び出すことができる。
#include <cstdio>

using namespace std;

class Shape {
public:
    Shape(int _x, int _y);
    int getx() { return x; };
    int gety() { return y; };
    virtual void draw();
private:
    int x, y;
};

class Rect : public Shape {
public:
    Rect(int _x, int _y, int _wid, int _hei);
    void draw();
private:
    int width, height;
};

class Circle : public Shape {
public:
    Circle(int _x, int _y, int _r);
    void draw();
private:
    int r;
};

Shape::Shape(int _x, int _y)
{
    x = _x;
    y = _y;
}

void Shape::draw()
{
    printf("draw Shape (%d %d)\n", getx(), gety());
}

Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
{
    width = _wid;
    height = _hei;
}

void Rect::draw()
{
    printf("draw Rect (%d %d)-(%d %d)\n",
        getx(), gety(), getx() + width, gety() + height);
}

Circle::Circle(int _x, int _y,  int _r) : Shape(_x, _y)
{
    r = _r;
}

void Circle::draw()
{
    printf("draw Circle C=(%d %d) r=%d\n", getx(), gety(), r);
}

int main()
{
    Rect rect(10, 20, 30, 15);
    Circle crc(100, 120, 25);
    Shape* pshp;

    pshp = &rect;
    pshp->draw();       // draw Rect (10 20)-(40 35)
    pshp = &crc;
    pshp->draw();       // draw Circle C=(100 120) r=25

    pshp->Shape::draw();    // draw Shape (100 120)
    return 0;
}
C++ クラスの継承 2024.07.15
仮想関数を継承したメンバ関数も仮想関数
CircleクラスはShapeの仮想関数であるdraw()を継承し再定義しているので、Circleのdraw()も暗黙に仮想関数になる。Ellipseクラスのdraw()は、Circleのdraw()の再定義としており、これもまた暗黙に仮想関数となる。
EllipseクラスのインスタンスをShapeクラスのポインタに代入してdraw()を呼び出すと、Shapeのdraw()の再定義のCircleのdraw()の再定義のEllipseのdraw()が最終的に呼び出される。Rectクラスも同様にShapeクラスポインタからのdraw()でRectクラスのdraw()が呼び出される。このしくみにより、各々の派生したオブジェクト毎の振る舞いを、同じ関数の呼び出し方法で実行させることができる(ポリモーフィズム)。
#include <cstdio>

using namespace std;

class Shape {
public:
    Shape(int _x, int _y);
    int getx() { return x; };
    int gety() { return y; };
    virtual void draw();
private:
    int x, y;
};

class Rect : public Shape {
public:
    Rect(int _x, int _y, int _wid, int _hei);
    void draw();
private:
    int width, height;
};

class Circle : public Shape {
public:
    Circle(int _x, int _y, int _r);
    void draw();
    int getr() { return r; };
private:
    int r;
};

class Ellipse : public Circle {
public:
    Ellipse(int _x, int _y, int _rh, int r_rv);
    void draw();
private:
    int rv;
};

Shape::Shape(int _x, int _y)
{
    x = _x;
    y = _y;
}

void Shape::draw()
{
    printf("draw Shape (%d %d)\n", getx(), gety());
}

Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
{
    width = _wid;
    height = _hei;
}

void Rect::draw()
{
    printf("draw Rect (%d %d)-(%d %d)\n",
        getx(), gety(), getx() + width, gety() + height);
}

Circle::Circle(int _x, int _y,  int _r) : Shape(_x, _y)
{
    r = _r;
}

void Circle::draw()
{
    printf("draw Circle C=(%d %d) r=%d\n", getx(), gety(), r);
}

Ellipse::Ellipse(int _x, int _y,  int _rh, int _rv) : Circle(_x, _y, _rh)
{
    rv = _rv;
}

void Ellipse::draw()
{
    printf("draw Ellipse C=(%d %d) rh=%d rv=%d\n", getx(), gety(), getr(), rv);
}

int main()
{
    Rect rect(10, 20, 30, 15);
    Ellipse elps(50, 70, 20, 35);
    Shape* pshp = &elps;
    pshp->draw();       // draw Ellipse C=(50 70) rh=20 rv=35
    pshp = &rect;
    pshp->draw();       // draw Rect (10 20)-(40 35)
    return 0;
}
C++ クラスの継承 2024.07.15
純粋仮想関数/抽象クラス
Shapeクラスのdraw()を仮想関数としてvirtualを宣言し、「=0」として実装を省略すると、純粋仮想関数となり、自らの実装を持たずに継承先で再定義されるためのプロトタイプだけの関数となる。
純粋仮想関数を持つクラスは、インスタンスを生成できない。継承先のサブクラスがインスタンス生成したときのコンストラクタでスーパークラスが生成される。Shapeクラスのインスタンスを生成しようとするとコンパイルエラーになる。もし、CircleクラスやEllipseクラスでdraw()を実装しなければ、それらのインスタンスも生成できない。
純粋仮想関数を持つクラスを「抽象クラス」と呼ぶ。
#include <cstdio>

using namespace std;

class Shape {
public:
    Shape(int _x, int _y);
    int getx() { return x; };
    int gety() { return y; };
    virtual void draw() = 0;
private:
    int x, y;
};

class Rect : public Shape {
public:
    Rect(int _x, int _y, int _wid, int _hei);
    void draw();
private:
    int width, height;
};

class Circle : public Shape {
public:
    Circle(int _x, int _y, int _r);
    void draw();
    int getr() { return r; };
private:
    int r;
};

class Ellipse : public Circle {
public:
    Ellipse(int _x, int _y, int _rh, int r_rv);
    void draw();
private:
    int rv;
};

Shape::Shape(int _x, int _y)
{
    x = _x;
    y = _y;
}

Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
{
    width = _wid;
    height = _hei;
}

void Rect::draw()
{
    printf("draw Rect (%d %d)-(%d %d)\n",
        getx(), gety(), getx() + width, gety() + height);
}

Circle::Circle(int _x, int _y,  int _r) : Shape(_x, _y)
{
    r = _r;
}

void Circle::draw()
{
    printf("draw Circle C=(%d %d) r=%d\n", getx(), gety(), r);
}

Ellipse::Ellipse(int _x, int _y,  int _rh, int _rv) : Circle(_x, _y, _rh)
{
    rv = _rv;
}

void Ellipse::draw()
{
    printf("draw Ellipse C=(%d %d) rh=%d rv=%d\n", getx(), gety(), getr(), rv);
}

int main()
{
    //Shape sh(10, 20);     // Error
    Rect rect(10, 20, 30, 15);
    Ellipse elps(50, 70, 20, 35);
    Shape* pshp = &elps;
    pshp->draw();       // draw Ellipse C=(50 70) rh=20 rv=35
    pshp = &rect;
    pshp->draw();       // draw Rect (10 20)-(40 35)
    return 0;
}
C++ クラスの継承 2024.07.15
スーパークラスのデストラクタは仮想関数にする
RectのインスタンスをShapeのポインタで参照して、Shapeのポインタをdeleteで削除すると、RectとShapeのデストラクタが順に呼び出されて、それぞれの終了処理が実行されることがわかる。
もし、Shapeの定義のデストラクタのvirtualを取り除くと、最後のShapeクラスのポインタpshpのdeleteは、Shapeのデストラクタしか実行されなくなり、表示は、
Shape deleted
だけになる。
スーパークラスのデストラクタに実装する内容が特になくても、virtualで空の仮想デストラクタを定義しておくべきである。
#include <cstdio>

using namespace std;

class Shape {
public:
    Shape(int _x, int _y);
    virtual ~Shape() { puts("Shape deleted"); };
    int getx() { return x; };
    int gety() { return y; };
private:
    int x, y;
};

class Rect : public Shape {
public:
    Rect(int _x, int _y, int _wid, int _hei);
    ~Rect() { puts("Rect deleted"); };
private:
    int width, height;
};

Shape::Shape(int _x, int _y)
{
    x = _x;
    y = _y;
}

Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
{
    width = _wid;
    height = _hei;
}

int main()
{
    Shape* pshp = new Rect(10, 20, 30, 15);
    delete pshp;
            // Rect deleted
            // Shape deleted
    return 0;
}
C++ クラスの継承 2024.07.15
継承のみアクセス許可するprotected
Shapeクラスのメンバx,yをprivateアクセスとした場合、派生先のサブクラスでも直接アクセスはできないので、Shapeクラスでgetx()やgety()のようなアクセス関数を用意する必要がある。
Shapeクラスのxとyをprotectedアクセス指定にすることで、Shapeクラスから派生したクラスのみpublicと同等にx,yを参照できるようになる。例では、Rectクラスのメンバ関数でShapeのxとyを直接参照できている。
protectedメンバは派生先以外ではprivate扱いとなる。もし、main()の中で「rect.x 」のように直接参照を試みてもコンパイルエラーとなる。
#include <cstdio>

using namespace std;

class Shape {
public:
    Shape(int _x, int _y);
    void draw();
protected:
    int x, y;
};

class Rect : public Shape {
public:
    Rect(int _x, int _y, int _wid, int _hei);
    void draw();
private:
    int width, height;
};

Shape::Shape(int _x, int _y)
{
    x = _x;
    y = _y;
}

void Shape::draw()
{
    printf("draw Shape (%d %d)\n", x, y);
}

Rect::Rect(int _x, int _y,  int _wid, int _hei) : Shape(_x, _y)
{
    width = _wid;
    height = _hei;
}

void Rect::draw()
{
    printf("draw Rect (%d %d)-(%d %d)\n",
        x, y, x + width, y + height);
}

int main()
{
    Rect rect(10, 20, 30, 15);
    rect.draw();            // draw Rect (10 20)-(40 35)
    return 0;
}