CPP03 1.0
|
EX00: オブジェクトの生成と基本操作
ClapTrap
というロボットクラスを作る。名前や体力を持たせ、攻撃や回復といった動作を実装する。OOPと基本的なクラスの形(Orthodox Canonical Class Form)の一部を実装して、オブジェクトのライフサイクル管理できるようにする。
EX01: 親子関係の導入(継承の基礎)
ClapTrap
を「親」として、新しいロボットScavTrap
を「子」として作り出す。子は親の基本的な性質を受け継ぎつつ、より特化した機能(ゲートキーパーモード)や異なる初期設定を持つことで少し進化したロボットとして登場する。ここでは、コードの再利用性という継承のメリットを理解し、親クラスの機能を拡張していく経験が得られます。継承:Inheritance, 派生クラス:Derived Class、基底クラス:Base Class, メソッドのオーバーライド:Method Overriding, protectedアクセス指定子:protected Access Specifier, コンストラクタとデストラクタの連鎖:Constructor and Destructor Chaining
EX02: 兄弟の登場(さらなる特化)
今度は
ScavTrap
とは異なる特性を持つFragTrap
という「兄弟」のような新しいロボットを作り出す。ClapTrap
の基本を受け継ぎながら、独自の機能(ハイタッチ)と異なる設定を持つ。同じ親を持つことなる子たちがそれぞれ異なる個性を持つ様子を理解して、継承が様々なオブジェクトを生み出す仕組みを学ぶ。
EX03: 複雑な関係性(多重継承の挑戦)
2つの親
FragTrap
,ScavTrap
の良いところを併せ持つDiamondTrap
という「ハイブリッド」なロボットを作り出す。複数の親から特性を受け継ぐ多重継承。ここでは多重継承の持つ力と潜在的な課題を学びます。
まとめ:
シンプルなオブジェクト作成から継承のメカニズムを通じてコードの再利用と拡張を体験し、最終的には複数の特性を組み合わせる多重継承という高度な概念へと、段階的に理解度を深めることができます。基本的なロボットから始まって、様々な機能を持つ特殊なロボットへと進化していく物語🤖✨
Doxygenで作成されたソースコードドキュメントです。
クラスの連携図やソースコードの説明に関する情報がまとめられています。
この課題で学ぶ機能や実装がなぜ必要なのか?その仕組みを使うとどうなるのか?歴史的な経緯やその効果に関する情報をまとめる。
ClapTrap.hpp , ClapTrap.cpp ,
* 汎用クラス「
ClapTrap
」を実装する。
- オブジェクトはHP, EnergyPoint, AttackDamage(攻撃力)のパラメータを持ち、攻撃・ダメージを受ける・自己修復することができる。
- ここでは基本的なオブジェクトの形を作ります。
個人的に気になった点:
オブジェクトの宣言と初期化を1行に書くと、コンパイラはコピーコンストラクタを呼び出すように設計されている。
ClapTrap.hpp , ClapTrap.cpp ,
* 基底クラス
ClapTrap
から派生クラスScavTrap
を実装します。(クラスの継承)
- 派生クラスで実現したい機能
ScavTrap
固有のコンストラクター
~ScavTrap()
,attack()
メソッドのオーバーライド (仮想関数)
guardGate()
メソッドの追加 (`ScavTrap`クラス固有の機能)
* 継承(
Inheritance
):ex01/ScavTrap.hpp
より抜粋
Represents a ScavTrap robot, a specialized type derived from ClapTrap.Definition ScavTrap.hpp:38基底クラス(
Base Class
):public ClapTrap
派生クラス(
Derived Class
):class ScavTrap
派生クラスは基底クラスの性質(メンバー変数やメンバー関数)を受け継ぐことができる仕組みで、コードの再利用+基底クラスからの機能拡張のメリットがある。
*
protected
アクセス指定子:ex01/ClapTrap.hpp
より抜粋
protected:std::string name;unsigned int hitPoints;unsigned int energyPoints;unsigned int attackDamage;派生クラス
ScavTrap
からメンバー変数に直接アクセスして変更できるようになる。*
Virtual
(仮想関数):ex01/ScavTrap.hpp
より抜粋
メソッドを派生クラスでオーバーライドできる。 (
ScavTrap
固有のAttack関数を作れる。)非仮想関数(
virtual
をつけないメンバー関数)virtual ~ScavTrap();基底クラスと派生クラスを仮想デストラクタにすることで、派生クラスのデストラクタが実行時に呼び出され、派生クラス固有雨のクリーンナップ処理が確実に行われ、メモリリークなどの問題を回避できる。
virtual
関数の仕組みと役割について、自分なりの解釈のまとめ。 * 派生クラスのオブジェクトは、基底クラスのインターフェース(関数やメンバー変数)を使ってオブジェクトの操作ができる。
- 派生クラスは基底クラスと異なる振る舞いをさせるために、固有のオブジェクトを作ることができる。
- そのために、専用のコンストラクターや固有メソッドを実装することや、固有のデストラクターによって安全にリソースの解放ができる。
- この固有メソッド、固有デストラクターを正しく呼び出すために、
virtual
メソッドとして定義する必要がある。* その他メモ
virtual
が付いていない同じ名前のメソッドの動作は未定義。virtual
が付いていると同じ名前でメソッドのオーバーライドができる。
ClapTrap.hpp , ClapTrap.cpp ,
* 継承:
ClapTrap
からFragTrap
クラスに継承する。
FragTrap
はハイタッチする機能を追加する。(フレンドリーなオブジェクト)- この課題はクラス継承の反復練習。
* コンストラクター、デストラクタ、代入演算子
- 仮想関数による関数のオーバーライド
- 固有のメンバー関数
highFivesGuys()
* オーバーライドしていない関数は
}void highFivesGuys(void)highFivesGuys function implementation for FragTrap.Definition FragTrap.cpp:96ClapTrap
クラスの関数が呼び出される。
今度は変だ!
ClapTrap.hpp , ClapTrap.cpp ,
* 多重継承とダイヤモンド問題
- 複数の基底クラス*
FragTrap
,ScavTrap
)から派生クラスDiamondTrap
を実装する。
各クラスの機能まとめ
テーブル 1: 各クラスのメンバー変数の初期値
メンバー変数 ClapTrap (デフォルト) ClapTrap (名前指定) ScavTrap (デフォルト) ScavTrap (名前指定) FragTrap (デフォルト) FragTrap (名前指定) DiamondTrap (デフォルト) DiamondTrap (名前指定) name "Default" 指定された名前 "DefaultScav" 指定された名前 "DefaultFrag" 指定された名前 "Default" 指定された名前 hitPoints 10 10 100 100 100 100 100 100 energyPoints 10 10 50 50 100 100 50 50 attackDamage 0 0 20 20 30 30 30 30 ClapTrap::name 該当なし 該当なし 該当なし 該当なし 該当なし 該当なし "Defalut_clap_name" 指定された名前 + "_clap_name" テーブル 2: 各クラスのメンバー関数の挙動
メンバー関数 ClapTrap ScavTrap FragTrap DiamondTrap attack -0 -20 -30 -30 takeDamage 引数の値 ← ← ← beRepaired 引数の値 ← ← ← guardGate - ☑︎ - ☑︎ highFivesGuys - - ☑︎ ☑︎ whoAmI - - - ☑︎ getName ☑︎ ☑︎ ☑︎ ☑︎ getHitPoints ☑︎ ☑︎ ☑︎ ☑︎ getEnergyPoints ☑︎ ☑︎ ☑︎ ☑︎ getAttackDamage ☑︎ ☑︎ ☑︎ ☑︎
* 多重継承のダイヤモンド構造
/ \\ /Represents a DiamondTrap robot, inheriting from both FragTrap and ScavTrap.Definition DiamondTrap.hpp:39
DiamondTrap
は2つのクラスから継承します。(ScavTrap
とFragTrap
)* 「ダイヤモンド」継承問題
例えば
main
でDiamondのオブジェクトの操作(takeDamage
やbeRepaired
のメソッド)を行うコードについて。mainの継承メソッド呼び出し。
DiamondTrap diamondUnit1("Gemini");DiamondTrap diamondUnit2("Sparkle");// ambiguousdiamondUnit2.takeDamage(40);diamondUnit1.beRepaired(25);クラスの定義
コンパイルエラーが起きる。
makemain.cpp:80:18: error: request for member ‘takeDamage’ is ambiguous80 | diamondUnit2.takeDamage(40);| ^~~~~~~~~~In file included from main.cpp:24:64 | void takeDamage(unsigned int amount);| ^~~~~~~~~~main.cpp:81:18: error: request for member ‘beRepaired’ is ambiguous81 | diamondUnit1.beRepaired(25);| ^~~~~~~~~~In file included from main.cpp:24:65 | void beRepaired(unsigned int amount);| ^~~~~~~~~~make: *** [Makefile:39: objs/main.o] Error 1なぜ?
この構造は 2つの異なるオブジェクトを介して クラスの継承するため、継承したメソッドを呼び出すためには、
::
スコープ解決演算子(scope resolution)を使って基底クラスの名前を指定する必要がある。対策1
::
スコープ解決演算子によるメソッド呼び出し。diamondUnit2.FragTrap::takeDamage(40);diamondUnit1.ScavTrap::beRepaired(25);対策2
基底クラスを
virtual
クラスとして定義する。( 仮想継承 )*
attack()
はScavTrap
のメソッドを使うためオーバーライドしない。(virtual
を付けない)
// Diamond.hpp の定義// Diamond.cpp のコードScavTrap::attack(target); // Use ScavTrap's attack}void attack(const std::string &target)Attack function implementation for DiamondTrap, using ScavTrap's attack.Definition DiamondTrap.cpp:87
クラス複数のパスから継承する問題で、クラスの基底クラスと同じ名前のメンバー変数宣言があると、その変数が隠蔽されて意図しない動作になることがある。これをシャドウィングという。
一般的な対策として、異なる名前を使用して解決する。
-Wshadow
フラグを付けることで、シャドウィングによる名前の競合に関する警告を出力します。シャドウィングはC++で扱うコードで発生する可能性がある。
-Wno-shadow
はシャドウィングを使用する特別の理由がある場合に使うことがあるが、これは慎重に行いましょう。
main
: 安定版
feature/ex00
: 各課題の機能開発を行う。
docs
: ここにpushすると、GitHub Actions workflowが動作してドキュメントが公開されます。
C++ Moduleのプロジェクト毎にDoxygenを使ったドキュメントをまとめています。
doxygen用のcssテーマは、Doxygen Awesomeを使用。