CPP04 1.0
|
このモジュールでは「ポリモーフィズム」の基礎から応用へと段階的に学習を深めていく課題です。
EX00: "Polymorphism" - ポリモーフィズム(多態性)の基本
異なる種類の動物を作る。
鳴き声が異なる。
基底クラス Animal は具体的な動物の鳴き声を発することができませんが、派生クラス Dog, Cat だとそれぞれ独自の音を出すことができる。
Animal Cat Dog Generic animal sound
Meow!
Woof!
ポリモーフィズムの基本的な仕組みを使って、これを実現する。(基底クラス、派生クラス、仮想関数)
また、仮想関数を使わなかった場合( WrongAnimal, WrongCat )の動作がも理解できる。
EX01: "I don’t want to set the world on fire" - 世界を燃やしたくない
「Deep Copy」の概念を学ぶ課題です。
Brain クラスを作成して、Dog と Cat のメンバーにポインター
Brain*
を持たせます。浅いコピー「Shallow Copy」と深いコピー「Deep Copy」の違いを理解し、クラス設計にDeep Copy を保証することで重大なメモリエラーを防ぐことができます。
堅牢なC++の基本要件として実践練習します。
EX02: "Abstract class" - 抽象クラス
EX00とEX01で作成した Animal クラスを「 抽象クラス 」にする課題です。
抽象クラスだけでのインスタンス化できない基底クラスであり、 Cat と Dog の共通のインターフェースを 純粋仮想関数 として宣言することで、派生クラスに特定の振る舞い(
makeSound()
)を強制する仕組みを学びます。
EX03: "Interface & recap" - インターフェースと総括
総括としての課題として、特に「 インターフェース 」の概念(C++98において純粋仮想関数のみを持つ抽象クラス)を用いた設計を実践する課題です。
魔法使いがいるゲーム世界観を実装することになります。 魔法 Materia という概念があり、具体的な魔法として Ice や Cure が存在します。 これらの魔法は MateriaSource の魔法テンプレートから生成され、魔法使い Character が装備して使うことができます。 ICharacter や IMateriaSource といった「インターフェース」は、魔法使いや魔法の源がどのように振る舞うべきかの「契約」を定義します。 この物語を通して、抽象クラス (純粋仮想関数のみを持つ) をインターフェースとして利用し、異なるクラス ( Character , MateriaSource ) が共通のインターフェースを通して連携し、多態的な振る舞い ( use() 関数など) を実現する方法を学びます。 また、魔法使いが魔法をコピーしたり ( clone() )、インベントリで管理したりする中で、深いコピーやメモリ管理の重要性が再び強調されます。
まとめ
EX00 から EX03 を順番に取り組むことで、ポリモーフィズムという抽象的な概念が、具体的な動物の鳴き声から始まり、オブジェクトの内部構造、抽象的な概念、そしてより複雑なオブジェクト間の関係性へと、段階的に理解を深めていくことができます。
Doxygenで作成されたソースコードドキュメントです。
クラスの連携図やソースコードの説明に関する情報がまとめられています。
ポリモーフィズムは、OOPの三大要素(継承、カプセル化、ポリモーフィズム)の1つ。
仮想関数、抽象クラス、インターフェースといったポリモーフィズムを実現するための C++ の言語機能を学習し使いこなせるようになるための課題です。
動物の鳴き声(ex00~ex02 )、魔法のアイテムとその使用者(ex03)
これらの例は、ゲーム開発やシミュレーションなどの分野でポリモーフィズムが実際に活用されています。
抽象的な概念をより身近な問題に落とし込まれた課題内容です。
ポリモーフィズム → 抽象クラス → インターフェース
それぞれの仕組みを課題を通して理解していきます。
CPP Modules 前半の課題で唯一経験値に直結する課題です。
Animal.hpp , Animal.cpp ,
Animal.hpp と WrongAnimal.hpp を分ける必要性がある?
基底クラスのポインタを使ったポリモーフィズム:シナリオ1
i->makeSound(); //will output the cat sound!j->makeSound();virtual void makeSound() constMakes a generic animal sound. This implementation is for the base Animal class. Derived classes are e...Definition Animal.cpp:66T endl(T... args)これは派生オブジェクトを基底クラスのポインタとして宣言されている。
- ポリモーフィズムの有効化
* 共通インターフェースオブジェクトのポインターを使って仮想関数を呼び出すと、オブジェクトの型によって呼び出される関数が決まる。
→ ランタイムポリモーフィズム
Runtime polymorphism
を実現することができる。(動的バインディングdynamic binding
or 遅延バインディングlate binding
)* 派生クラスの固有メンバーへのアクセス制限Animalのインターフェースを使えば、異なるオブジェクトの操作を共通のコードで行うことができる。
→ Animalポインタを操作する汎用的なコード
* 仮想デストラクタの重要性
const Animal *j
を通じて、Animalクラスで定義されたメンバーにのみアクセスできる。(Animalのpublic/protectedのメンバー)Dog固有のメソッドを(
Dog::bark()
)使うためには、 dynamic_cast が必要。if (actualDog) {actualDog->bark(); // Only possible after casting}void bark() constMakes a specific barking sound for a Dog. This is a Dog-specific method.Definition Dog.cpp:88オブジェクトを削除は、
Dog::~Dog()
→Animal::~Animal()
の順に呼び出す必要がある。Animalクラスに仮想デストラクタ(
virtual ~Animal();
)を持たせることで、派生クラス固有のリソースでのメモリリークを防ぐことができる。
派生クラスポインタを使ったを使った場合:シナリオ2
j->makeSound(); // Calls Dog::makeSound() -> "Woof!" (Compiler knows j is a Dog*)i->makeSound(); // Calls Cat::makeSound() -> "Meow!" (Compiler knows i is a Cat*)virtual void makeSound() constMakes the characteristic sound of a cat ("Meow!"). This function overrides the virtual makeSound() fr...Definition Cat.cpp:65virtual void makeSound() constMakes the characteristic sound of a dog ("Woof!"). This function overrides the virtual makeSound() fr...Definition Dog.cpp:65派生オブジェクトを指すポインタは、派生クラス正確な型のポインタを宣言している。
- ポリモーフィズムなし
コンパイルの時点でオブジェクトの正確な型が決まっている。
- 仮想関数の決定
ポインタの型がオブジェクトの型と一致するため、コンパイル時に呼び出される関数が決まる。
- 全ての派生メンバーへのアクセス
// If Dog had a specific bark() method:j->bark(); // Directly accessible派生クラス固有のメソッドを含む public / protect のメンバーに直接アクセスできる。
- 柔軟性が低いコード
// Example: an array (not possible with different derived types directly)1つの配列にグループ化することができない。
(DogやCatを1つの配列やコレクションにまとめられない)
- デストラクタの挙動
派生クラスポインタを使ってオブジェクトを削除する場合、
Animal::~Animal()
が仮想かに関係なく、常に正しいデストラクタDog::~Dog()
/Cat::~Cat()
が最初に呼ばれる。なぜ?→コンパイラが削除されるオブジェクトの正確な型を認識しているため。
まとめると
基底クラスのポインタを使った最初のシナリオは、ポリモーフィズムの効果が発揮することができる。
複数の異なる派生オブジェクトをコレクションしたり、それらの操作するコードを共通化でき、それぞれ独自の関数は実行時に自動的に呼び出される。
結果、柔軟性が高く、保守性の高いオブジェクト指向システムを構築するための基礎となる考え方です。
Class : Brain
Animal.hpp , Animal.cpp ,
* Brain クラス:
100個の std::string 型の配列を含む。
setIdea()
とgetIdea()
メソッドを追加コンストラクタでは Brain オブジェクトを動的に生成する。
デストラクタで適切に delete することでメモリリークを防ぐ。
また、コピーコンストラクタとコピー代入演算子を実装する。(Deep Copy)
* 動的メモリ割り当て(new, delete)
- ポインタによるオブジェクトの管理
- メモリリークの防止
- 深いコピー(Deep Copy)と浅いコピー(Shallow Copy)
- コピーコンストラクタ、コピー代入演算子
- デストラクタ(基底デストラクタが virtual であることが重要)
- 基底クラスポインタによる派生クラスオブジェクトの配列管理
- ポリモーフィズムによるデストラクタの呼び出し
Animal.hpp , Animal.cpp , ← 更新
* Animal クラスを抽象化クラス( Abstract Class ) にする。
仕組み
純粋仮想関数( Pure Virtual Function )がクラス内に1つでも存在する場合、そのクラスは自動的に抽象的クラスになります。
抽象クラスでは実装せず、派生クラスの実装が義務付けられる。
効果:デフォルトの Animal クラスはインスタンス化できない。
* EX00 と EX02 の Animal の違い
インスタンス化:
EX00 ... インスタンス化できた。
EX02 ... インスタンス化できない。
仮想関数( EX00 )・純粋仮想関数( EX02 )
virtual void makeSound() const; // EX00 仮想関数
:実装あり
virtual void makeSound() const = 0; // EX02 純粋仮想関数
:実装なしDog や Cat の動物だけがインスタンス化でき、 makeSound() を提供することで、ポリモーフィズムがより強く強制される。
Class : AMateria Cure Ice IMaterialSource MateriaSource ICharacter Character
AMateria.hpp , AMateria.cpp ,
MateriaSource.hpp , MateriaSource.cpp ,
Character.hpp , Character.cpp ,
ICharacter.hpp , IMateriaSource.hpp , <- この2つがインターフェース
AMateria という 抽象クラス を定義し、具体的な Materia の種類として Ice と Cure クラスを実装します。
それぞれの Materia は Cure::clone() , Ice::clone() 関数で自身のコピーを生成し、 Cure::use() , Ice::use() 関数で特定の効果を発揮します。
また、ICharacter というインターフェースを定義し、それを実装する Character クラスを作成します。
Character は Materia を装備・装備解除・使用するためのインベントリを持ち、コピー時には深いコピーが行われるように設計します。
さらに、 IMateriaSource という インターフェース と、それを実装する MateriaSource クラスを作成し、Materia の「学習」と「生成」の仕組みを実装します。
セルフボーナスとして、 main 関数で各クラスを使う時のエラーハンドル(特に new 失敗に関する考え方やリソースを安全に解放すること)について学びました。
詳しくは、RAII ( Resource Acquisition Is Initialization ) にまとめます。
main
: 安定版
feature/ex00
: 各課題の機能開発を行う。
docs
: ここにpushすると、GitHub Actions workflowが動作してドキュメントが公開されます。
C++ Moduleのプロジェクト毎にDoxygenを使ったドキュメントをまとめています。
doxygen用のcssテーマは、Doxygen Awesomeを使用。