継承
既存クラスを土台にして、共通部分をまとめて拡張する。
継承の考え方
継承は、共通する振る舞いを基底クラスにまとめ、 派生クラスで差分だけを定義する仕組みです。
コードの重複を減らし、設計の見通しを良くします。
基本的な継承
#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 デストラクタが設計の要点