第47回 確認問題(継承と多態)
STEP 7 の 継承・仮想関数・vtable・抽象クラス・多重継承 の総合確認クイズ 12 問。
進め方
- 9 問以上正解で STEP 8(テンプレート)へ進める状態
- 苦手カテゴリは該当章で復習
継承
継承の基本
0 / 2
Q1 継承時の基底クラスのコンストラクタ呼び出し順は?
派生 → 基底
基底 → 派生
同時
プログラマが選ぶ
基底 → 派生の順。派生クラスは基底のメンバを使えるので、先に基底が初期化されます。破棄は逆順。
Q2 Animal a = dog; で起きるのは?
コンパイルエラー
参照が自動で取られる
スライシング(派生部分が切り落とされる)
多態性が働く
派生を基底の値型に代入するとスライシング。多態性を活かすには参照・ポインタ・unique_ptr を使う必要があります。
virtual
仮想関数
0 / 3
Q3 virtual なしの関数を基底ポインタから呼ぶとどうなる?
派生の実装が呼ばれる
基底の実装が呼ばれる(静的バインディング)
コンパイルエラー
実行時エラー
virtual なしだと静的バインディング。ポインタの型(基底)で呼び出し先が決まるので、基底の実装が呼ばれます。多態が必要なら virtual を付ける。
Q4 override を付ける最大のメリットは?
実行速度が上がる
タイポ/シグネチャ違いをコンパイル時に検出できる
必須のキーワード
派生クラスが final になる
override は「基底に virtual 関数がある」ことを保証。タイポ・const 付け忘れ等の事故を防ぎます。
Q5 仮想関数を持つクラスのデストラクタを virtual にしないと何が起きる?
特に問題ない
仮想関数も動かなくなる
基底ポインタから delete で派生の dtor が呼ばれず未定義動作
コンパイルエラー
virtual なしだと基底の dtor しか呼ばれず、派生部分のリソースリーク。基底クラスには必ず virtual ~Base() = default;。
vtable
vtable
0 / 2
Q6 仮想関数を持つクラスのオブジェクトに追加されるのは?
vtable 本体
参照カウンタ
vptr(vtable へのポインタ)
型名の文字列
オブジェクトごとに vptr が追加。vtable 自体はクラスごとに 1 つ。
Q7 基底コンストラクタ内で仮想関数を呼ぶと?
派生の実装が呼ばれる
基底の実装が呼ばれる(vptr はまだ基底の vtable を指している)
コンパイルエラー
null ポインタ参照
コンストラクタは基底 → 派生の順で動き、その途中では vptr は基底の vtable を指しています。そのため基底 ctor 内で仮想呼び出しすると基底の版が呼ばれる。落とし穴のひとつ。
抽象
抽象クラス
0 / 3
Q8 純粋仮想関数の正しい宣言は?
virtual void f();
abstract void f();
virtual void f() = 0;
void f() override = 0;
C++ は末尾に = 0 を付ける特殊構文。abstract キーワードは存在しません。
Q9 抽象クラスに対してできることは?
インスタンス化
関数を直接呼ぶ
ポインタ・参照として使う(派生を指す)
何もできない
抽象クラスは直接インスタンス化できませんが、ポインタ・参照の型としては使え、派生を指すのが普通の使い方。
Q10 純粋仮想関数をすべて override しない派生クラスの状態は?
具象クラス(インスタンス化可)
抽象クラスのまま(インスタンス化不可)
コンパイルエラー
実行時エラー
未実装が残っていれば派生も抽象。さらに派生して全部実装した時点で初めて具象に。
多重
多重継承
0 / 2
Q11 菱形継承の問題を解決する方法は?
派生クラスで再定義
中間クラスで virtual 継承を使う
final を使う
解決不可
class B : virtual public A で virtual 継承すると、A のインスタンスが 1 つに統合されます。
Q12 C++ で多重継承を安全に使う推奨パターンは?
深い継承階層
全基底を virtual 継承
多重継承は禁止
単一継承 + 純粋インターフェース(データメンバ無し)の多重実装
データメンバを持たない純粋インターフェースなら菱形問題が起きません。「extends 1 つ + implements 複数」の Java 風スタイル。