継承

既存クラスを土台にして、共通部分をまとめて拡張する。

継承の考え方

継承は、共通する振る舞いを基底クラスにまとめ、 派生クラスで差分だけを定義する仕組みです。

コードの重複を減らし、設計の見通しを良くします。

基本的な継承

#include <iostream>
#include <string>

class Animal {
protected:
    std::string name;

public:
    Animal(const std::string& n) : name(n) {}

    void sleep() const {
        std::cout << name << " は眠っています" << std::endl;
    }

    virtual void sound() const {
        std::cout << name << ": 音声" << std::endl;
    }
};

class Dog : public Animal {
public:
    Dog(const std::string& n) : Animal(n) {}

    void sound() const override {
        std::cout << name << ": ワンワン" << std::endl;
    }

    void fetch() const {
        std::cout << name << " はボールを取ってきた" << std::endl;
    }
};

class Cat : public Animal {
public:
    Cat(const std::string& n) : Animal(n) {}

    void sound() const override {
        std::cout << name << ": ニャアニャア" << std::endl;
    }
};

int main() {
    Dog dog("ポチ");
    Cat cat("タマ");

    dog.sound();
    dog.sleep();
    dog.fetch();

    cat.sound();
    cat.sleep();

    return 0;
}

ポチ: ワンワン
ポチ は眠っています
ポチ はボールを取ってきた
タマ: ニャアニャア
タマ は眠っています

アクセス修飾子と継承

public / protected / private の設計は、 継承時の使いやすさに直結します。

#include <iostream>
#include <string>

class Account {
protected:
    std::string owner;
    double balance;

public:
    Account(const std::string& o, double b)
        : owner(o), balance(b) {}

    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }

    virtual void printSummary() const {
        std::cout << owner << " / 残高: " << balance << std::endl;
    }
};

class SavingAccount : public Account {
private:
    double interestRate;

public:
    SavingAccount(const std::string& o, double b, double rate)
        : Account(o, b), interestRate(rate) {}

    void addInterest() {
        balance += balance * interestRate;
    }

    void printSummary() const override {
        std::cout << owner << " / 残高: " << balance
                  << " / 金利: " << interestRate * 100 << "%" << std::endl;
    }
};

int main() {
    SavingAccount account("山田太郎", 100000, 0.01);
    account.deposit(5000);
    account.addInterest();
    account.printSummary();
    return 0;
}

山田太郎 / 残高: 106050 / 金利: 1%

仮想関数とポリモーフィズム

基底クラスのポインタや参照を通して呼ぶと、 実際の派生クラスの実装が動きます。

#include <iostream>
#include <memory>
#include <vector>

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
    virtual void draw() const = 0;
};

class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    double area() const override {
        return 3.14159 * radius * radius;
    }

    void draw() const override {
        std::cout << "円を描画" << std::endl;
    }
};

class Rectangle : public Shape {
private:
    double width, height;

public:
    Rectangle(double w, double h) : width(w), height(h) {}

    double area() const override {
        return width * height;
    }

    void draw() const override {
        std::cout << "長方形を描画" << std::endl;
    }
};

void render(const Shape& shape) {
    shape.draw();
    std::cout << "面積: " << shape.area() << std::endl;
}

int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>(5));
    shapes.push_back(std::make_unique<Rectangle>(4, 3));

    for (const auto& shape : shapes) {
        render(*shape);
    }

    return 0;
}

円を描画
面積: 78.5398
長方形を描画
面積: 12

override と final

override はオーバーライドの意図を明示し、 final は継承や上書きを止めます。

class Base {
public:
    virtual void run() const {
        std::cout << "Base::run" << std::endl;
    }
};

class Derived final : public Base {
public:
    void run() const override {
        std::cout << "Derived::run" << std::endl;
    }
};

int main() {
    Derived d;
    d.run();
    return 0;
}

Derived::run

よくある誤り

誤り1: 基底クラスのデストラクタを virtual にしない

class Base {
public:
    ~Base() { }
    virtual void f() = 0;
};

誤り2: override を付け忘れる

class Child : public Base {
public:
    void run() const { }
};

誤り3: 基底クラスの state を private のまま派生で無理に触る

class BadBase {
private:
    int value;
};

ポイント

  • 継承は共通化と拡張のための仕組み
  • protected は派生クラスにだけ見せたい要素に使う
  • virtual と override で多態性を安全に表現する
  • virtual デストラクタは基底クラスで重要
  • final で継承の境界を明示できる

やってみよう

練習1: Vehicle / Car / Bike の継承階層を作る

練習2: protected メンバを使う会計クラスを作る

練習3: Shape 系に draw() を追加して描画処理を統一する

チャレンジ: virtual デストラクタの必要性を実験で確認する

まとめ

  • 継承で共通処理をまとめると保守しやすい
  • virtual と override で派生クラスの振る舞いを切り替える
  • アクセス制御と virtual デストラクタが設計の要点