高度なOOP

継承の先にある、実践的な設計の細部を整える。

多重継承

1つのクラスに複数の特性を組み合わせたいときに使います。 便利ですが、設計が複雑になりやすいので、用途を絞るのが重要です。

#include <iostream>

class Flyable {
public:
    virtual void fly() { std::cout << "飛行中" << std::endl; }
    virtual ~Flyable() = default;
};

class Swimmable {
public:
    virtual void swim() { std::cout << "泳ぎ中" << std::endl; }
    virtual ~Swimmable() = default;
};

class Duck : public Flyable, public Swimmable {
public:
    void quack() { std::cout << "クワッ" << std::endl; }
};

int main() {
    Duck duck;
    duck.fly();
    duck.swim();
    duck.quack();
    return 0;
}

飛行中
泳ぎ中
クワッ

仮想デストラクタ

基底クラスを介して派生クラスを破棄する可能性があるなら、 仮想デストラクタは必須です。

#include <iostream>
#include <memory>

class Base {
public:
    virtual ~Base() {
        std::cout << "Base破棄" << std::endl;
    }
};

class Derived : public Base {
public:
    ~Derived() override {
        std::cout << "Derived破棄" << std::endl;
    }
};

int main() {
    {
        std::unique_ptr<Base> ptr = std::make_unique<Derived>();
    }
    return 0;
}

Derived破棄
Base破棄

委譲とコンポジション

継承だけではなく、委譲で機能をまとめると柔軟性が上がります。

#include <iostream>
#include <string>

class Printer {
public:
    void print(const std::string& text) const {
        std::cout << text << std::endl;
    }
};

class ReportService {
private:
    Printer printer;

public:
    void outputReport(const std::string& title) {
        printer.print("Report: " + title);
    }
};

int main() {
    ReportService service;
    service.outputReport("売上レポート");
    return 0;
}

Report: 売上レポート

型キャストと安全性

ダウンキャストは便利ですが、必ず安全性を確認してから使います。

#include <iostream>
#include <memory>

class Shape {
public:
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    void roll() {
        std::cout << "転がる" << std::endl;
    }
};

int main() {
    std::unique_ptr<Shape> shape = std::make_unique<Circle>();

    if (auto circle = dynamic_cast<Circle*>(shape.get())) {
        circle->roll();
    }

    return 0;
}

転がる

pImpl イディオム

実装詳細を隠したいときは、pImpl のような手法で依存を減らせます。 公開ヘッダを小さく保てるのが利点です。

よくある誤り

誤り1: 継承で無理に状態共有しすぎる

誤り2: virtual なしで基底ポインタを delete する

誤り3: dynamic_cast の結果確認を省く

ポイント

  • 多重継承は局所的に使うと便利
  • 仮想デストラクタは破棄の安全性に直結する
  • 継承と委譲を使い分けると設計が安定する
  • dynamic_cast は安全確認付きで使う
  • pImpl は実装隠蔽に向く

やってみよう

練習1: Flyable と Swimmable を組み合わせた別の動物を作る

練習2: Base ポインタ経由で delete して破棄順を確認する

練習3: pImpl を使った軽いクラス設計を考える

まとめ

  • 高度なOOPでは継承だけでなく委譲も重要
  • 安全な破棄と安全なキャストが実践で効く
  • Phase 9 では STL を設計面から扱う