MST

星途 面试题库

面试题:C++ 虚基类在复杂继承体系中的应用

假设存在一个较为复杂的继承体系,A为顶层类,B和C继承自A,D同时继承B和C,E继承自D。请使用虚基类的方式实现该继承体系,并解释在构造函数和析构函数调用顺序上需要注意哪些问题,以及如何避免可能出现的内存泄漏等风险。
23.5万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试
#include <iostream>

class A {
public:
    A() { std::cout << "A constructor" << std::endl; }
    ~A() { std::cout << "A destructor" << std::endl; }
};

class B : virtual public A {
public:
    B() { std::cout << "B constructor" << std::endl; }
    ~B() { std::cout << "B destructor" << std::endl; }
};

class C : virtual public A {
public:
    C() { std::cout << "C constructor" << std::endl; }
    ~C() { std::cout << "C destructor" << std::endl; }
};

class D : public B, public C {
public:
    D() { std::cout << "D constructor" << std::endl; }
    ~D() { std::cout << "D destructor" << std::endl; }
};

class E : public D {
public:
    E() { std::cout << "E constructor" << std::endl; }
    ~E() { std::cout << "E destructor" << std::endl; }
};

构造函数和析构函数调用顺序注意事项

  1. 构造函数顺序
    • 首先调用虚基类(这里是A)的构造函数,无论虚基类在继承体系中的位置如何,它总是第一个被构造。这确保了虚基类子对象在其他派生类构造之前被正确初始化。
    • 然后按照继承列表中出现的顺序调用非虚基类的构造函数,即BC(在D类中),最后调用自身(DE)的构造函数。
  2. 析构函数顺序
    • 析构函数的调用顺序与构造函数相反。首先调用自身(ED)的析构函数,然后按照继承列表中出现的相反顺序调用非虚基类的析构函数(CB),最后调用虚基类(A)的析构函数。

避免内存泄漏风险

  1. 正确管理资源:在构造函数中分配的资源,要在对应的析构函数中正确释放。例如,如果在某个类的构造函数中使用new分配了内存,那么在析构函数中要使用delete释放该内存。
  2. 遵循RAII原则:使用智能指针(如std::unique_ptrstd::shared_ptr)来管理动态分配的资源。这样可以确保资源在对象生命周期结束时自动释放,避免手动管理内存可能出现的遗漏释放导致的内存泄漏。例如,如果类中持有动态分配的数组,可以使用std::unique_ptr<int[]>来管理,而不是直接使用int*
  3. 确保虚析构函数:如果基类有虚函数,基类的析构函数应该声明为虚函数。这样在通过基类指针删除派生类对象时,会调用正确的派生类析构函数,从而保证派生类对象的资源被正确释放。在上述例子中,A类如果有虚函数,其析构函数应声明为虚函数virtual ~A() { std::cout << "A destructor" << std::endl; }