MST

星途 面试题库

面试题:C++ 复杂继承体系下析构函数虚拟的深入探讨及内存管理

在一个复杂的 C++ 继承体系中,有如下类定义: ```cpp class GrandParent { public: GrandParent() { std::cout << "GrandParent constructor" << std::endl; } virtual ~GrandParent() { std::cout << "GrandParent destructor" << std::endl; } void someFunction() { std::cout << "GrandParent someFunction" << std::endl; } }; class Parent : public GrandParent { public: Parent() { std::cout << "Parent constructor" << std::endl; } ~Parent() override { std::cout << "Parent destructor" << std::endl; } void someFunction() override { std::cout << "Parent someFunction" << std::endl; } int* data; Parent() : data(new int(0)) {} }; class Child : public Parent { public: Child() { std::cout << "Child constructor" << std::endl; } ~Child() override { std::cout << "Child destructor" << std::endl; delete data; } void someFunction() override { std::cout << "Child someFunction" << std::endl; } }; ``` 1. 解释为什么 `GrandParent` 的析构函数需要是虚拟的,如果 `GrandParent` 的析构函数不是虚拟的,会发生什么情况,特别是在涉及 `delete` 一个指向 `Child` 对象的 `GrandParent*` 指针时。 2. 在上述代码中,如果我们不小心在 `Parent` 的析构函数中忘记写 `delete data;`,会导致什么内存管理问题,如何通过设计更好地避免这类问题(比如利用智能指针等技术),请详细说明并修改代码。
17.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试
    • 为什么GrandParent的析构函数需要是虚拟的: 在C++中,当通过基类指针删除派生类对象时,如果基类的析构函数不是虚拟的,那么只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类中分配的资源(如动态分配的内存等)无法正确释放,从而产生内存泄漏。在上述代码中,如果GrandParent的析构函数不是虚拟的,当delete一个指向Child对象的GrandParent*指针时,只会调用GrandParent的析构函数,ParentChild的析构函数不会被调用,Child对象中动态分配的data内存就无法释放。

    • 如果GrandParent的析构函数不是虚拟的,在delete指向Child对象的GrandParent*指针时的情况: 只会调用GrandParent的析构函数,输出“GrandParent destructor”。ParentChild的析构函数不会被调用,Parent类中data所指向的内存没有被释放,造成内存泄漏。

    • 忘记在Parent的析构函数中写delete data;导致的内存管理问题: 会导致内存泄漏。因为data是在Parent类的构造函数中通过new分配的内存,如果在析构函数中没有delete data;,那么当Parent对象或其派生类Child对象被销毁时,data所指向的内存不会被释放。

    • 利用智能指针避免这类问题: 可以使用std::unique_ptr来管理动态分配的内存。std::unique_ptr在对象销毁时会自动调用其管理对象的析构函数,从而确保内存正确释放。修改后的代码如下:

#include <memory>
#include <iostream>

class GrandParent {
public:
    GrandParent() { std::cout << "GrandParent constructor" << std::endl; }
    virtual ~GrandParent() { std::cout << "GrandParent destructor" << std::endl; }
    void someFunction() { std::cout << "GrandParent someFunction" << std::endl; }
};

class Parent : public GrandParent {
public:
    Parent() { std::cout << "Parent constructor" << std::endl; }
    ~Parent() override { std::cout << "Parent destructor" << std::endl; }
    void someFunction() override { std::cout << "Parent someFunction" << std::endl; }
    std::unique_ptr<int> data;
    Parent() : data(std::make_unique<int>(0)) {} 
};

class Child : public Parent {
public:
    Child() { std::cout << "Child constructor" << std::endl; }
    ~Child() override { std::cout << "Child destructor" << std::endl; }
    void someFunction() override { std::cout << "Child someFunction" << std::endl; }
};

在上述代码中,std::unique_ptr<int>代替了int*std::make_unique<int>(0)用于创建int对象并由std::unique_ptr管理。这样,在ParentChild对象销毁时,std::unique_ptr会自动释放其所管理的内存,避免了内存泄漏。