要用一个类型的实例去继承另一个类型的属性时,可能会用到虚析构函数
虚析构函数不是覆写原析构函数,而是再加上一个新的析构函数,他会先调用派生类析构函数,再调用基类虚构函数
例:#include<iostream>
int main()
{
class Base
{
public:
Base() {std::cout << "Base Constructor\n"; };
~Base() { std::cout << "Base Destructor\n"; };
};
class Entity: public Base
{
int* m_Array;
public:
Entity(){m_Array = new int[10]; std::cout << "Entity Constructor\n";}
~Entity(){delete[] m_Array; std::cout << "Entity Destructor\n";}
};
Base* e = new Base;
delete e;
std::cout << "=========================\n";
Base* f = new Entity;
delete f;
}
Base* f = new Entity; 这行代码发生了几个关键的事情,涉及到面向对象编程中的多态性、继承和动态内存分配。
-
多态性:多态性允许你使用基类的指针或引用来操作派生类的对象。在这个例子中,Entity 是从 Base 类派生出来的,所以你可以使用 Base 类的指针来指向一个 Entity 类的对象。这是多态性的一个典型应用。
-
继承:Entity 类通过 public 继承从 Base 类继承了所有的公有和保护成员。这意味着 Entity 类是 Base 类的一个特化版本,它拥有 Base 类的所有特性,并且可以添加或覆盖自己的特性。由于 Entity 是 Base 的子类,所以你可以创建一个 Entity 对象,并使用 Base 类的指针来指向它。
-
动态内存分配:new 关键字用于在堆上动态分配内存。在这个例子中,new Entity 创建了一个 Entity 类的实例,并在堆上为其分配了内存。new 表达式返回的是指向新分配对象的指针,这个指针的类型是 Entity*,但由于多态性,你可以安全地将其赋值给 Base* 类型的变量 f。
-
向上转型:将派生类的指针赋值给基类指针的过程被称为向上转型(upcasting)。这是安全的,因为派生类对象总是可以看作是其基类的对象。在这个例子中,Entity* 到 Base* 的转换是一个向上转型。
-
对象构造和析构:当使用 new Entity 创建对象时,首先会调用 Base 类的构造函数(因为 Entity 是从 Base 继承的),然后是 Entity 类的构造函数。对象的析构过程与此相反:首先调用 Entity 类的析构函数,然后是 Base 类的析构函数。这确保了对象的正确初始化和清理。
当使用 delete f; 时,由于 f 是一个基类指针,它只知道如何调用基类的析构函数。如果基类的析构函数不是虚函数(virtual),那么派生类的析构函数将不会被调用,这可能导致资源泄漏(在这个例子中,m_Array 没有被正确删除)。为了避免这个问题,应该在基类中将析构函数声明为虚函数:
class Base | |
{ | |
public: | |
Base() { std::cout << "Base Constructor\n"; } | |
virtual ~Base() { std::cout << "Base Destructor\n"; } // 注意这里的 virtual 关键字 | |
}; |
这样,当 delete f; 被调用时,会首先调用 Entity 类的析构函数,然后调用 Base 类的析构函数,从而正确释放了 m_Array 指向的内存。