多态中的虚拟析构函数 类
Virtual destructor in polymorphic classes
我知道只要你有一个多态基 class,基 class 就应该定义一个虚拟析构函数。这样当一个指向derived-class对象的base-class指针被删除时,它会先调用derivedclass的析构函数。如果我在这里错了,请纠正我。
此外,如果 base-class 析构函数是非虚拟的,则删除指向派生对象的 baseclass 指针将是未定义的行为。如果我也错了,请纠正我。
所以我的问题是:为什么当 base-class 析构函数是非虚函数时,对象不会被正确销毁?
我假设这是因为虚函数有某种 table ,每当调用虚函数时都会记住和查询。并且编译器知道当一个对象应该被删除时,它应该首先调用派生的析构函数。
我的假设正确吗?
如果在删除对象时变量的静态类型是bas类型,则将调用基类型的析构函数,但子class的析构函数不会被调用(因为它不是虚拟的)。
因此baseclass分配的资源会被释放,subclass分配的资源不会。
因此对象不会被正确销毁。
你说得对 table:它被称为虚拟方法 table 或 "vtable"。但是析构函数非虚的结果并不是析构函数没有按正确的顺序调用,而是子class(es)的析构函数根本没有被调用!
考虑
struct Base {
void f() { printf("Base::f"); }
};
struct Derived : Base {
void f() { printf("Derived::f"); }
};
Base* p = new Derived;
p->f();
这会打印 Base::f
,因为 Base::f
不是虚拟的。现在对析构函数做同样的事情:
struct Base {
~Base() { printf("Base::~Base"); }
};
struct Derived : Base {
~Derived() { printf("Derived::~Derived"); }
};
Base* p = new Derived;
p->~Base();
这会打印 Base::~Base
。现在,如果我们将析构函数设为虚函数,那么与任何其他虚函数一样,将调用对象动态类型中的最终覆盖程序。析构函数覆盖基 class 中的虚拟析构函数(即使它的 "name" 不同):
struct Base {
virtual ~Base() { printf("Base::~Base"); }
};
struct Derived : Base {
~Derived() override { printf("Derived::~Derived"); }
};
Base* p = new Derived;
p->~Base();
调用 p->~Base()
实际上调用了 Derived::~Derived()
。由于这是一个析构函数,在它的主体完成执行后,它会自动调用基类和成员的析构函数。所以输出是
Derived::~Derived
Base::~Base
现在,delete-expression 通常等同于先调用析构函数再调用内存释放函数。在这种特殊情况下,表达式
delete p;
相当于
p->~Base();
::operator delete(p);
因此,如果析构函数是虚拟的,则这样做是正确的:它首先调用 Derived::~Derived
,然后在完成后自动调用 Base::~Base
。如果析构函数不是虚拟的,可能的结果是只调用 Base::~Base
。
我知道只要你有一个多态基 class,基 class 就应该定义一个虚拟析构函数。这样当一个指向derived-class对象的base-class指针被删除时,它会先调用derivedclass的析构函数。如果我在这里错了,请纠正我。
此外,如果 base-class 析构函数是非虚拟的,则删除指向派生对象的 baseclass 指针将是未定义的行为。如果我也错了,请纠正我。
所以我的问题是:为什么当 base-class 析构函数是非虚函数时,对象不会被正确销毁?
我假设这是因为虚函数有某种 table ,每当调用虚函数时都会记住和查询。并且编译器知道当一个对象应该被删除时,它应该首先调用派生的析构函数。
我的假设正确吗?
如果在删除对象时变量的静态类型是bas类型,则将调用基类型的析构函数,但子class的析构函数不会被调用(因为它不是虚拟的)。
因此baseclass分配的资源会被释放,subclass分配的资源不会。
因此对象不会被正确销毁。
你说得对 table:它被称为虚拟方法 table 或 "vtable"。但是析构函数非虚的结果并不是析构函数没有按正确的顺序调用,而是子class(es)的析构函数根本没有被调用!
考虑
struct Base {
void f() { printf("Base::f"); }
};
struct Derived : Base {
void f() { printf("Derived::f"); }
};
Base* p = new Derived;
p->f();
这会打印 Base::f
,因为 Base::f
不是虚拟的。现在对析构函数做同样的事情:
struct Base {
~Base() { printf("Base::~Base"); }
};
struct Derived : Base {
~Derived() { printf("Derived::~Derived"); }
};
Base* p = new Derived;
p->~Base();
这会打印 Base::~Base
。现在,如果我们将析构函数设为虚函数,那么与任何其他虚函数一样,将调用对象动态类型中的最终覆盖程序。析构函数覆盖基 class 中的虚拟析构函数(即使它的 "name" 不同):
struct Base {
virtual ~Base() { printf("Base::~Base"); }
};
struct Derived : Base {
~Derived() override { printf("Derived::~Derived"); }
};
Base* p = new Derived;
p->~Base();
调用 p->~Base()
实际上调用了 Derived::~Derived()
。由于这是一个析构函数,在它的主体完成执行后,它会自动调用基类和成员的析构函数。所以输出是
Derived::~Derived
Base::~Base
现在,delete-expression 通常等同于先调用析构函数再调用内存释放函数。在这种特殊情况下,表达式
delete p;
相当于
p->~Base();
::operator delete(p);
因此,如果析构函数是虚拟的,则这样做是正确的:它首先调用 Derived::~Derived
,然后在完成后自动调用 Base::~Base
。如果析构函数不是虚拟的,可能的结果是只调用 Base::~Base
。