日本語ブログ

C++コンストラクタ継承の面接対策完全ガイド

2026年5月10日2 分で読める
Office meeting in group

C++コンストラクタ継承の面接対策を、初期化リスト・構築順序・using・落とし穴まで整理。答え方をそのまま練習できます。

C++ のコンストラクタ継承に関する質問で答えに詰まる候補者の多くは、知識が足りないのではなく、順序が抜けています。基底クラスのコンストラクタが最初に実行されること、初期化リストが何らかの形で関わること、そして modern C++ には `using` キーワードがあることは、なんとなく知っています。ですが、面接官に「派生クラスでコンストラクタがどう動くか説明してください」と言われると、その曖昧さは一気に崩れます。この c++ constructor inheritance interview preparation のガイドでは、まず暗記しやすい答えを示し、その後に構築順序、最後に、序盤を抜けたあとで面接官が仕掛けてくる落とし穴を取り上げます。

目的は、教科書の要約ではなく、実際に声に出して言えるスクリプトです。

まず答えを言う: コンストラクタは継承されない

20 秒で口に出せる答え

白板に書き終わる前の面接官に対して、できる候補者が返す答えはこれです。

"C++ ではコンストラクタは継承されません。派生オブジェクトを作るとき、派生側のコンストラクタは初期化リストで基底クラスのコンストラクタを明示的に呼び出します。基底クラスは、派生コンストラクタ本体が実行される前に最初に構築されます。基底コンストラクタの呼び出しを指定しない場合、コンパイラは基底のデフォルトコンストラクタを呼び出そうとしますが、デフォルトコンストラクタが存在しなければコンパイルできません。"

これで十分です。ここまで言えれば、核心の質問には答えられています。初期化リストの仕組み、構築順序、modern な `using` 構文は、すべて後続の話題です。ただ、面接官が見ているのは、コンストラクタは継承されないこと、そして基底は初期化リストで明示的に呼び出して構築することを理解しているかどうかです。この 2 点が答えのすべてです。

実際のコードではこう見える

ホワイトボードで示すなら、これは 30 秒で描ける図です。面接官は、基底コンストラクタの呼び出し場所が本体ではなく初期化リストであること、そして引数を明示的に渡していることを確認できます。 cppreference.com によると、コンストラクタはデフォルトでは継承されません。各クラスは自分のコンストラクタを定義する責任を持ち、派生クラスは適切な基底クラスのコンストラクタを明示的に呼び出す必要があります。このルールが土台です。ほかはすべて、そこから導かれます。

基底コンストラクタは本体ではなく初期化リストで呼ぶ

初期化リストこそが本当の仕組み

C++ のコンストラクタ初期化リストは、単なる書き方の好みではありません。基底サブオブジェクトやメンバ変数が実際に構築される仕組みそのものです。コンストラクタ本体の最初の `{` が開く時点で、基底クラスのサブオブジェクトはすでに完全に構築されていなければなりません。これは慣習ではなく、言語仕様上そうなっています。

本体の中で基底コンストラクタを「呼ぶ」ことはできません。その時点では基底はすでに構築済みです(デフォルトコンストラクタがあればそれで構築されています)。本体で行っているのは、触っている対象によっては何もしないか、あるいは代入です。基底クラスの初期化には代入に相当するものはありません。C++ のコンストラクタ初期化リストだけが、その構築が起こる唯一の場です。

実際のコードではこう見える

注釈付きのほうが、処理順を具体的に示せます。`: Base(x)` が先に実行され、基底サブオブジェクトが生成され、その後に `{}` の本体が動きます。本体内で `Base(x)` を書いても、名前のない一時的なオブジェクトが即座に破棄されるだけで、`Derived` の中に埋め込まれている実際の `Base` サブオブジェクトには何の影響もありません。GCC はこの誤りを警告してくれないことが多く、単に間違った動作を静かに行います。まさに面接官が狙っている罠です。

Bjarne Stroustrup の C++ FAQ と ISO C++ 標準のどちらも、本文より前に初期化することをオブジェクト構築の基本ルールとして扱っています。候補者がこれを正しく説明できれば、構文だけでなくオブジェクトのライフタイムを理解していることが示せます。

デフォルトコンストラクタがない場合は推測しない

基底にデフォルトコンストラクタがないと壊れる理由

基底クラスが引数付きコンストラクタのみを持ち、デフォルトコンストラクタを持たない場合、コンパイラは他に逃げ道がありません。派生クラスの初期化リストに基底クラスのコンストラクタ呼び出しが必要です。書かれていなければ、コンパイラは引数なしで基底クラスのコンストラクタを呼ぼうとし、該当するものがないためコンパイルエラーになります。これはコンパイラの癖ではなく、構造上そうなるだけです。

面接で重要なのは、「基底コンストラクタを呼び忘れた」という表現ではないことです。正しくは、「コンパイラが存在しないコンストラクタを呼ぼうとした」ということです。この違いを理解していると、コンパイラが何を代行しているのかまで分かっていると示せます。

実際のコードではこう見える

これを GCC で通すと、たとえば次のようなエラーになります。

修正版はシンプルです。派生コンストラクタの初期化リストで必要な引数を渡します。

実際にビルドでこのエラーに当たったことがある候補者は、すぐに気づきます。資料で読んだだけの候補者は、エラーメッセージの向きを逆に覚えていることがよくあります。`no matching function for call to 'Base::Base()'` を見たことがあれば、考えなくても意味が分かります。

面接で続けて聞かれること: 基底コンストラクタが private または protected だったら?

面接官は、デフォルトコンストラクタがない話のあとで、よくアクセス権の話に進みます。基底コンストラクタが private なら、派生クラスは初期化リストであっても呼び出せません。派生クラスは基底サブオブジェクトをまったく構築できないため、通常の意味ではそのクラスを継承できません。

基底コンストラクタが protected なら、派生クラスからはアクセスできますが、外部コードからはアクセスできません。継承を許可しつつ、基底クラスの直接インスタンス化は防ぎたい場合に使う想定のパターンです。この違いを知っている候補者は、コンストラクタの可視性を単なる文法ではなく設計手段として理解しています。

順序を覚える: 基底が先、派生が後、破棄は逆順

なぜ順序を覚えること自体より重要なのか

構築順序は任意ではありません。派生クラスの本体やメンバは、すでに生きている基底サブオブジェクトに依存していることがあります。基底のメソッドを呼んだり、基底のフィールドを読んだり、基底コンストラクタが確立する不変条件に頼ったりします。もし派生が先に動くなら、そうした依存先は未初期化メモリを触ることになります。言語が基底→派生の順を規定しているのは、まさにその種のバグを防ぐためです。

破棄が逆順なのも同じ理由です。派生デストラクタは、まだ基底サブオブジェクトを使っている可能性があるので、先に実行されます。派生デストラクタが終わったあとで、基底サブオブジェクトが破棄されます。基底を先に壊してしまうと、派生デストラクタが死んだオブジェクトを触ることになります。

実際のコードではこう見える

端末出力:

これは、C++ の継承に関する面接でホワイトボードに書くと非常に有用な例です。出力結果が、言葉だけの説明よりも順序を具体的に示してくれます。順序を本当に理解しているか、それともルールを暗記しただけかを試す面接官は、出力の予測を求めてきます。頭の中で実行して正しい答えにたどり着ければ、それが証拠です。 cppreference の派生クラス構築に関する説明 でも、この順序は仕様どおりの動作として確認できます。

コンストラクタ継承とコンストラクタチェーンは分けて考える

面接官がこの違いを好む理由

C++ のコンストラクタ継承とコンストラクタチェーンは、十分似ているので、候補者が本番で混同しがちです。しかしこれは別の仕組みです。コンストラクタ継承(`using Base::Base;`)は、基底クラスのコンストラクタを派生クラスのインターフェースに公開するものです。コンストラクタチェーン(デリゲーティングコンストラクタ)は、同じクラス内のあるコンストラクタが、同じクラス内の別のコンストラクタを呼ぶ仕組みです。面接でこれを混同すると、信頼性に傷がつきます。面接官が細かいからではなく、候補者がどちらの機能も意図して使ったことがないと分かってしまうからです。

実際のコードではこう見える

左側が C++ のコンストラクタ継承です。基底のコンストラクタを派生クラスに取り込み、呼び出し側が `Derived` オブジェクトを `Base` と同じシグネチャで直接構築できるようにします。右側が委譲です。同じクラスのコンストラクタ同士で処理を再利用し、初期化ロジックの重複を避けます。両方を一言でまとめるなら、「継承は公開し、委譲は再利用する」です。これを言えて、正しい構文も示せる候補者なら、続きの質問にもきれいに答えられます。

どちらの機能も cppreference の C++11 標準追加事項 にある継承コンストラクタで扱われており、デリゲーティングコンストラクタも同じ標準改訂で規定されています。

面接官が次に実際に聞くフォローアップに答える

using Base::Base; は C++11 以降でどうなるのか?

`using Base::Base;` は、基底クラスの各コンストラクタのシグネチャに一致する派生コンストラクタをコンパイラに生成させる宣言です。これはシンタックスシュガーです。内部では、基底サブオブジェクトは依然として初期化リストで構築され、ルールはすべて同じままです。変わるのはボイラープレートだけで、基底の各シグネチャごとに派生コンストラクタを書いて引数を転送する必要がなくなるという点です。

罠は、これで根本ルールが変わると考えてしまうことです。変わりません。基底サブオブジェクトは依然として構築される必要があります。派生クラスが基底初期化を飛ばしたり、上書きしたりすることはできません。`using Base::Base;` は繰り返しを減らすための便利機能であって、従来の意味で本当にコンストラクタを継承する仕組みではありません。これを聞く面接官は、候補者が「何ができて、何ができないか」を理解しているかを確かめています。

継承階層でコピーコンストラクタはどうなるのか?

コピーコンストラクタとムーブコンストラクタも、同じく基底先行のルールに従います。`Derived` オブジェクトがコピー構築されるとき、`Derived` のコピーコンストラクタは `Base` のコピーコンストラクタを呼び出す必要があります。通常は、初期化リストで `: Base(other)` と書きます。ここで `other` は元の `Derived` オブジェクトで、同時に `Base` でもあります。`Derived` 用のカスタムコピーコンストラクタを書いて、`Base` 部分のコピー構築を忘れると、基底サブオブジェクトはデフォルト構築されてしまい、基底の状態を静かに失います。

これは面接用の罠というだけでなく、実運用コードでも起こる本物のバグです。ルールは同じで、基底部分は別途初期化リストで処理し、派生コンストラクタが正しく責任を持つ必要があります。

オブジェクトスライシングや仮想ディスパッチがここで出てくるのはなぜか?

オブジェクトスライシングと仮想ディスパッチは、どちらも同じ根本問題に関係しています。つまり、派生オブジェクトが常に派生オブジェクトとして扱われるわけではない、ということです。

スライシングは、`Derived` オブジェクトを `Base` を受け取る関数に値渡ししたときに起こります。`Derived` 部分は文字通り切り落とされ、`Base` サブオブジェクトだけがコピーされます。これを防ぐコンストラクタの魔法はありません。値セマンティクスの帰結です。対策は、ポインタか参照で渡すことです。

コンストラクタ内での仮想ディスパッチは別の罠です。基底クラスのコンストラクタが仮想関数を呼んでも、期待どおりにはディスパッチされません。その時点の vtable ポインタは `Derived` ではなく `Base` を指しているためです。まだ `Derived` は完全には構築されていません。したがって、派生のオーバーライドではなく基底版の仮想関数が実行されます。仮想関数は常に「最も派生した版」を呼ぶと思い込んでいる候補者には意外に映ります。コンストラクタ中(またはデストラクタ中)では、その瞬間に構築中の型が動的型です。 Herb Sutter の GotW #50 でこの点は詳しく説明されており、上級 C++ 面接の前に読んでおく価値があります。

多重継承とダイヤモンドには油断しない

多重継承で話が変わる理由

単一基底なら構築順序は単純です。複数の基底を持つ場合、コンパイラはクラス定義の基底クラスリストに現れる順序で基底を構築します。初期化リストに書いた順ではありません。この違いは、初期化リストを並べ替えれば順序を制御できると考える候補者を引っかけます。できません。順序はクラス定義で固定です。

ダイヤモンド問題ではさらに話が複雑になります。2 つの基底クラスが共通の祖先を共有していると、素朴な実装ではその祖先が 2 回構築されます。1 本の経路ごとに 1 回ずつです。仮想継承は、この 2 つのコピーを 1 つにまとめるために存在しますが、その代わり、共有基底を誰が構築するのかが変わります。仮想継承階層では、最も派生したクラスが仮想基底を直接構築する責任を持ち、中間クラスのその基底へのコンストラクタ呼び出しは迂回されます。

実際のコードではこう見える

`virtual` がなければ、`A` は `D` の中で 2 回構築されます。`virtual` があると、`A` サブオブジェクトは 1 つだけ共有され、その構築責任は `D` にあります。`B` と `C` の初期化リストにある `A(x)` 呼び出しは、`D` を通して構築するときには静かに無視されます。これは直感に反するので、面接官もそれを知っています。だからこそ聞くのです。`D` が `A` を直接初期化しなければならない理由まで説明できれば、サブオブジェクト配置を本当に理解していると示せます。

ISO C++ FAQ の仮想継承に関する説明 はこれを正確に扱っており、面接でソースを求められたときに引用するのに適した参考文献です。

Verve AI で C++ のコンストラクタ継承面接対策を強化する

この記事でたどってきた構造上の問題、つまりルールを知ること、順序を知ること、落とし穴を知ることは、読めば確かに学べます。ですが、実際に声に出した答えが「理解して言えている」のか「圧力の中で組み立て直している」のかを、読書だけで判断するのは難しいです。候補者には同じに感じられても、面接官にはまったく違って見えます。

そのギャップを埋めるために作られているのが Verve AI Interview Copilot です。Verve AI Interview Copilot は、リアルタイムで聞き取り を行い、用意された定型文ではなく、実際に話した内容に反応し、面接官が本当に次に聞きそうなフォローアップを提示します。たとえば、このガイドで扱った「基底コンストラクタが private だったら?」や「コンストラクタ内の仮想ディスパッチはどうなる?」といった質問です。Verve AI Interview Copilot はこの処理の間も目立たず動作するので、練習環境は実際の面接の圧力に近くなり、途中で止めて調べる安全網はありません。

C++ 対策で特に重要なのは、Verve AI Interview Copilot があなたの言い方そのものに反応できる点です。もし「基底が先に動く」とだけ言って理由を説明しなければ、上級面接官と同じように、その「なぜ」を掘り下げてきます。このフィードバックループを、このガイドの落とし穴ごとに繰り返すことで、知識はためらわずに出せる答えへと変わります。

結論

20 秒のスクリプトがすべてです。コンストラクタは継承されず、派生コンストラクタは初期化リストで基底コンストラクタを呼び、基底は最初に構築され、最後に破棄されます。これを明確に言い、初期化リストの構文を示し、構築順序をたどれば、核心の質問と最初の 2 つのフォローアップまで、面接官が促す前にカバーできます。

落とし穴、つまりデフォルトコンストラクタがない場合、基底コンストラクタが private の場合、コンストラクタ内の仮想ディスパッチ、ダイヤモンド問題は、すべて同じルールの延長です。ルールを自分のものにできれば、落とし穴は予測可能になります。次の面接の前に、核心の答えを声に出して練習してください。読むのではなく、言うのです。知っていることと、圧力の中で出せることの差こそ、まさに準備で埋めるべきギャップです。

VA

Verve AI

コンテンツ