ProgrammingC++ 開発者

C++における仮想関数と仮想継承のメカニズムはどのように機能しますか?抽象インターフェースを持つクラスを設計する際の特徴は何ですか?

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

答え

仮想関数はポリモーフィズムを実現します。これは、派生クラスのオブジェクトを基底クラスのポインタや参照を通じて処理する能力です。仮想関数を宣言するには、virtualキーワードを使用します:

class Base { public: virtual void foo() { std::cout << "Base::foo"; } }; class Derived : public Base { public: void foo() override { std::cout << "Derived::foo"; } };

Base*型のポインタでfoo()を呼び出すと、Derivedを指している場合、Derivedの実装が呼ばれます。

抽象クラスは、少なくとも1つの純粋仮想関数を含みます:

class Interface { public: virtual void process() = 0; // 純粋仮想関数 };

仮想継承はダイヤモンド問題を解決します(diamond problem):

class A { }; class B : virtual public A { }; class C : virtual public A { }; class D : public B, public C { };

これにより、オブジェクトDに基底クラスAの唯一のインスタンスが存在することが保証されます。

トリックのある質問

仮想デストラクタと通常のデストラクタの違いは何ですか?継承のためにクラスが設計されている場合、デストラクタを仮想にすべきですか?

答え: 仮想デストラクタは、基底型ポインタを介して削除する際に派生クラスのデストラクタが呼び出されることを保証します。これはリソースを正しく解放するために重要です。

class Base { public: virtual ~Base() {} };

デストラクタが仮想でない場合、delete BasePtr;を実行すると、派生クラスのオブジェクトではBaseのデストラクタのみが呼び出され、継承されたフィールドのリソースは解放されません。

トピックに関する実際のミスの例


物語

さまざまなツールに共通のクラスインターフェースを持つ大規模な金融システムで、仮想デストラクタが宣言されていませんでした。ある派生クラスで動的リソースが使用されていました。基底型ポインタを通じてオブジェクトを削除した際にメモリリークが発生し、大規模な負荷テストの段階で発見されました。


物語

チームは仮想継承を使わずに多重継承を使用していました。クラスDは、間接クラスを通じてAを二度継承していました。これにより、状態が重複し、Aのメンバーへのアクセス時にエラーが発生しました。静的解析ツールを使用した監査の後にのみ修正されました。


物語

ロギングプラグインの開発プロジェクトで、抽象クラスを使用しましたが、デストラクタを仮想にしませんでした。インターフェースポインタを介してプラグインを削除する際に、明示的でない依存関係や子クラスのデストラクタが呼び出されないことに関連するバグが発生しました。この問題はリソースプールに及び、リソースリークを引き起こしました。