MST

星途 面试题库

面试题:C++常对象在复杂继承体系中的应用及问题分析

在一个复杂的C++继承体系中,基类有若干成员函数和成员变量,派生类继承自基类并进行了函数重写和新成员的添加。当在派生类中定义常对象时,从运行时多态、访问控制以及内存管理等方面,分析常对象调用基类和派生类成员函数可能出现的问题及解决方法。请通过具体的代码示例,并结合底层原理进行深入剖析。
36.7万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

运行时多态方面

  1. 问题:当派生类常对象调用虚函数时,如果基类虚函数没有被声明为 const,会导致编译错误。因为常对象只能调用常成员函数,而非常成员函数无法修改常对象的状态。
  2. 解决方法:将基类中的虚函数声明为 const,这样派生类重写的虚函数也会自动成为 const 成员函数,常对象就可以调用。
class Base {
public:
    virtual void func() const {
        // 基类虚函数,被声明为 const
        std::cout << "Base::func()" << std::endl;
    }
};

class Derived : public Base {
public:
    void func() const override {
        // 派生类重写的虚函数,也为 const
        std::cout << "Derived::func()" << std::endl;
    }
};

int main() {
    const Derived d;
    Base* ptr = &d;
    ptr->func(); // 运行时多态,调用 Derived::func()
    return 0;
}

底层原理:C++通过虚函数表(vtable)实现运行时多态。当对象为常对象时,编译器会检查调用的成员函数是否为 const,如果不是则报错。虚函数表中存储的是虚函数的地址,通过对象的虚指针(vptr)找到对应的虚函数表,从而实现运行时多态。

访问控制方面

  1. 问题:如果基类成员变量或函数的访问修饰符为 private,派生类常对象无法直接访问。即使派生类重写了成员函数,如果重写函数访问了基类 private 成员,常对象调用派生类函数也可能出现问题。
  2. 解决方法:将基类需要被派生类访问的成员设为 protected 或提供 public 的访问接口(如 getset 函数)。
class Base {
protected:
    int data;
public:
    Base(int value) : data(value) {}
    int getData() const {
        return data;
    }
};

class Derived : public Base {
public:
    Derived(int value) : Base(value) {}
    void printData() const {
        // 访问基类 protected 成员
        std::cout << "Data: " << getData() << std::endl;
    }
};

int main() {
    const Derived d(10);
    d.printData();
    return 0;
}

底层原理:编译器在编译阶段根据访问修饰符检查对象对成员的访问权限。private 成员只能在类内部访问,protected 成员可以在派生类中访问,public 成员可以在任何地方访问。

内存管理方面

  1. 问题:如果基类没有虚析构函数,当通过基类指针删除派生类常对象时,可能导致内存泄漏。因为不会调用派生类的析构函数,派生类中分配的资源无法释放。
  2. 解决方法:将基类析构函数声明为虚析构函数。
class Base {
public:
    virtual ~Base() {
        std::cout << "Base::~Base()" << std::endl;
    }
};

class Derived : public Base {
private:
    int* arr;
public:
    Derived() {
        arr = new int[10];
    }
    ~Derived() override {
        delete[] arr;
        std::cout << "Derived::~Derived()" << std::endl;
    }
};

int main() {
    Base* ptr = new Derived();
    delete ptr;
    return 0;
}

底层原理:当基类析构函数为虚时,通过基类指针删除派生类对象,会根据对象实际类型调用相应的析构函数。如果基类析构函数不是虚的,delete 操作只会调用基类析构函数,导致派生类资源无法释放。