404 Not Found

404 Not Found


nginx

C++における多態性

[第]31[課では]継承について学びました――[派生クラスが]基底クラスの[コードを][再利用]する仕組みです。

[しかし実際の場面では]、[baseクラスのポインタを使って、異なる派生クラスのオブジェクトを統一的に処理する必要がある]。

[これこそが]ポリモーフィズム――「[1つの]インターフェース、[複数の実装]」。


1. 多態性とは何か?

(1) 1.1 [日常生活における]多型

[生活シーン] 番組[での対応]
[犬に]「[吠え]」と言うと、[犬は]「[ワンワン]」と吠える [基底クラスのポインタ] [を呼び出す] speak()、[実際には]派生クラスの[バージョン]が実行される
[猫に]「[鳴け]」と言うと、[猫は]「[ニャーニャー]」と鳴く [同じ]関数を[呼び出す]場合でも、[異なる]オブジェクトでは[挙動が異なる]

ポリモーフィズム[の本質]: [基底クラスのポインタが]派生クラスのオブジェクトを[指し示している]場合、[仮想関数を呼び出すと][動的バインディング]により[派生クラスの実装]に[結び付けられる]。

(2) 1.2 なぜ多態性が必要なのか?

[不要]多態性([面倒]):

▶ サンプル 2: オブジェクト指向[プログラミングデモ] (難易度 ⭐)

CPP
#include iostream

class Dog {
public:
 void speak() { std::cout << "!" << std::endl; }
};

class Cat {
public:
 void speak() { std::cout << "!" << std::endl; }
};

int main() {
 Dog* d = new Dog();
 Cat* c = new Cat();
 
 d->speak();
 c->speak();
 // ❌
 
 return 0;
}
▶ 試してみよう

[用]多態性([簡潔]):

CPP
#include iostream

class Animal {
public:
 virtual void speak() { std::cout << "Animal speaks" << std::endl; }
};

class Dog : public Animal {
public:
 void speak() override { std::cout << "!" << std::endl; }
};

class Cat : public Animal {
public:
 void speak() override { std::cout << "!" << std::endl; }
};

int main() {
 Animal* animals = {new Dog(), new Cat()};
 
 for (int i = 0; i < 2; i++) {
 animals[i]->speak(); // ✅ polymorphism:calling
 }
 
 return 0;
}


2. 仮想関数(virtual)

基本的な使い方

[基底クラス]内で[関数]をvirtualとして宣言し、[派生クラス]内でoverride([オーバーライド])を行う。

CPP
#include iostream
#include string

class Animal {
protected:
 std::string name;
 
public:
 Animal(const std::string& n) : name(n) {}
 
 // ...
 virtual void speak() {
 std::cout << name << " makes a sound." << std::endl;
 }
 
 //!
 virtual ~Animal() {}
};

class Dog : public Animal {
public:
 Dog(const std::string& n) : Animal(n) {}
 
 void speak() override {
 std::cout << name << "says:!" << std::endl;
 }
};

int main() {
 Animal* a = new Dog("Buddy");
 a->speak(); // ✅ output:Buddy says:!
 
 delete a; // ✅ calling Dog 'sdestructor,calling Animal 's
 return 0;
}

⚠️ [重要]: 基底クラスに仮想関数が[ある]場合、デストラクタも[明示的に宣言する]必要があります virtual![そうしないと] delete 基底クラスのポインタを経由して派生クラスのデストラクタが[呼び出されません]。


(2) 2.2 [キーワード]のオーバーライド(C++11)

C++11では、overrideという[キーワード]が[導入され]、これによりコンパイラが[仮想関数が実際にオーバーライドされているかどうかを]チェックしてくれるようになりました。

CPP
class Dog : public Animal {
public:
 // ✅ override,compilation
 void speak() override { ... }
 
 // ❌ write( void speak(int x)),compilation
 // void speak(int x) override { ... } //:
};

💡 [アドバイス]: [常に] overrideを追加——[コンパイラに]バグの検出を[任せる]。



3. 純粋仮想関数と抽象クラス

(1) 3.1 純粋仮想関数とは何か?

純粋仮想関数とは、基底クラス内で[宣言のみ行われ、実装は行われない]仮想関数のことであり、= 0で[マーク]されます。

CPP
class Animal {
public:
 // ...
 virtual void speak() = 0;
 
 virtual ~Animal() {}
};

[純粋仮想関数を持つ]クラスは[「抽象クラス」と呼ばれる]——[インスタンス化はできず]、[基底クラスとしてのみ使用できる]。

CPP
Animal a; // ❌ compilation:
Animal* p = new Dog("Buddy"); // ✅ Usingpointer

(2) 3.2 なぜ純粋仮想関数が必要なのか?

[派生クラス]は[この]関数を[必ず][オーバーライド]**しなければなりません。

CPP
class Animal {
public:
 virtual void speak() = 0; // ...
};

class Dog : public Animal {
public:
 void speak() override {
 std::cout << "!" << std::endl;
 }
 // ✅ write speak(),compilation
};


4. 仮想関数[の]仕組み([選択科目])

(1) 4.1 仮想関数[表](vtable)

C++ [用語]仮想関数[表](vtable)[実装]多態性:

  1. [仮想関数]を持つクラスに対して、コンパイラはvtable([仮想関数のアドレスを格納する]テーブル)を生成する。
  2. [各]オブジェクト[には、隠された] vptr([vtableを]指す[ポインタ])が含まれている
  3. [仮想関数を呼び出す際]、vptr [を通じて] [正しい]関数アドレスを特定する

💡 [ヒント]: [これが、仮想関数を持つ]クラスやオブジェクトが[ポインタ分のサイズ](64[ビットシステムでは]8[バイト])を余分に占める理由です。




❓ よくある質問

Q:[なぜ]仮想関数だけでなく、デストラクタも[仮想でなければならない]のですか? A: [もし] delete [が] 派生クラスのオブジェクト[を指す] 基底クラスのポインタを[削除した]場合、[かつ] 基底クラスの[デストラクタが] [仮想でない] 場合、[その際は] 基底クラスの[デストラクタのみが呼び出され]、派生クラスの[リソースがリークしてしまう]。

CPP
Animal* a = new Dog("Buddy");
delete a; // ❌ [もし] ~Animal() が仮想関数でなければ、Dog のデストラクタは呼び出されない

[黄金律]: 基底クラスに[仮想関数]がある場合、~base class() [必ず] virtualでなければならない。

Q:override [と] final [の違いは]? A:> - override:コンパイラに、この関数が基底クラスの仮想関数をオーバーライドしていることを伝える(コンパイラにチェックを任せる) > - final:コンパイラに、この関数はオーバーライドできないことを伝える(派生クラス内で使用する場合)

CPP
class Dog : public Animal {
public:
void speak() override final { ... } // final:派生クラスでは[speak()を再定義できない]
};

Q:[いつ]ポリモーフィズムを使うべきですか?** A:[異なる]型のオブジェクトを[統一された]インターフェースで[扱う]必要がある[とき]です。

[代表的なシナリオ]:

  • [ゲーム]:[全キャラクター]の継承[元] Character、[用] Character* [一元管理]
  • GUI:[すべてのコントロール]の継承[自] Widget、[用] Widget* [統一描画]
  • ファイル[システム]:[すべて]fileinheritance[自] File、[用] File* [統一読み書き]

📖 まとめ


📝 練習問題

  1. 初心者(難易度 ⭐): [定義する] Vehicle 基底クラス。このクラスには、[純粋仮想関数] void move() が含まれている。CarBicycle が [これを] 継承し、[それぞれ] 「[車が道路を走行している]」と「[自転車が自転車道を走行している]」を出力するようにする。

  2. 中級(難易度 ⭐⭐): [上記の]プログラムを[拡張]し、void stop()という仮想関数を[追加]して、Carが「[車が路肩に停車]」と出力し、Bicycleが「[自転車がブレーキをかける]」と出力するように[する]。

  3. 上級(難易度 ⭐⭐⭐): [[従業員給与システム]の設計:]

  4. Employee [抽象]基底クラス([氏名]、[社員番号]、[給与計算] virtual double calcSalary() = 0

  1. 仮想関数:基底クラス[宣言]、派生クラス[オーバーライド]、実行時多態性

6. 🚀 次は

[ポリモーフィズムを学びました]。[次は] テンプレート(Templates)を学びます([第]33[回])―― [関数や]クラス[が任意の]型に対応できるようにし、[真の]「ジェネリック[プログラミング]」を実現しましょう!

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%