ProgrammingC++システム/インフラ開発者

関数と変数の検索順序について(名前のルックアップ、スコープ)、および特に仮想継承における階層における名前のあいまいさに関する問題点を教えてください。これらの衝突をどのように解決しますか?

Hintsage AIアシスタントで面接を突破

答え

C++では、名前の検索順序は名前解決のルール(name lookup)によって規制されており、スコープ、隠蔽(hiding)、継承の特性を考慮しています。主なポイントは以下の通りです:

  • メソッドを呼び出す際や変数にアクセスする際、検索は派生スコープから基底スコープへと行われます。
  • 複数の継承の場合、名前が両方の基底ブランチに出現した場合、あいまいさ(ambiguity)が発生します。
  • 仮想継承はダイヤモンド問題の重複を避けるのに役立ちますが、名前の明示的な解決も必要です。

衝突の解決方法:

  • 子クラスで隠蔽された関数には、usingを使用できます:
class Base { public: void foo(int) {} }; class Derived : public Base { public: using Base::foo; void foo(double) {} // オーバーロードされたバージョン };
  • 同じ名前の変数や関数に対しては:
    • 基底クラスの名前を明示的に指定する必要があります:Base::xまたはBase::foo()

ダイヤモンド継承におけるあいまいさの例:

struct A { int x; }; struct B : virtual A {}; struct C : virtual A {}; struct D : B, C { void test() { x = 42; } // OK:xは仮想によって1つだけ };

トリッキーな質問

基底クラスでメソッドfoo(int)が宣言され、派生クラスでfoo(double)が宣言された場合、派生クラスのオブジェクトからfoo(int)にアクセスできますか?

答え: いいえ、メソッドfoo(double)は基底クラスのすべてのオーバーロードされたバージョンのfooを「隠蔽」します。foo(int)にアクセスするには、明示的にusing Base::fooを記述する必要があります。

例:

class Base { public: void foo(int) { } }; class Derived : public Base { public: void foo(double) {} }; Derived d; d.foo(3); // エラー!foo(int)は見えません

使用する:

class Derived : public Base { public: using Base::foo; void foo(double) {} }; d.foo(3); // すべて動作します

このテーマの詳細を知らなかったための実際のエラーの例


物語

複数の継承を持つプロジェクトで、2つの基底から同じメソッドfoo()を継承したクラスが出現しました。著者は仮想継承を使用せず、基底クラスのコピーが2つ作成され、すべてのfoo()への呼び出しがあいまいになり、コンパイラはコードをビルドすることを拒否しました。これは、仮想継承を適用し、呼び出し時にBase::foo()を明示的に指定することで解決されました。


物語

グラフィックスエンジンでは、1つの子クラスがdraw()を定義せず、基底クラスのdraw()へのusingを行わなかったため、コードをリファクタリングした結果、適切なバージョンのdraw()が呼び出されず、インターフェースの一部が表示されなくなりました。このエラーは、クラス構造を深く分析した後に発見されました。


物語

新しいコンパイラに移行した際、複数かつ仮想的な継承による名前のあいまいさのために、コンパイルが不可能になりました。以前は動作していた実装は、変わったインクルードのチェーンによって異なる名前のセットをもたらし、トランスレーションユニット内でのそれに対処していませんでした。