代码分析
class A
{
A();
virtual ~A();
};
class B : public A
{
};
class C : public B
{
};
A* a = new C;
delete a;
问题解答
1. A、B、C 都是虚拟类吗?
- A 类:明确声明了虚析构函数
virtual ~A();
,所以 A 是虚拟类(多态类) - B 类:虽然没有显式声明虚函数,但因为继承了 A 的虚析构函数,所以 B 也是虚拟类
- C 类:同样继承了 B(间接继承 A)的虚析构函数,所以 C 也是虚拟类
2. delete a;
调用的析构顺序
当执行 delete a;
时,由于 A 的析构函数是 virtual 的,会发生多态析构,调用顺序如下:
- C 的析构函数(编译器自动生成的)
- B 的析构函数(编译器自动生成的)
- A 的析构函数(用户定义的虚析构函数)
这是典型的析构顺序:从最派生类到基类。
3. 关键点说明
- 虚析构函数的重要性:如果 A 的析构函数不是 virtual 的,
delete a;
将只会调用 A 的析构函数,导致 B 和 C 的部分未被正确销毁(内存泄漏) - 自动生成的析构函数:B 和 C 没有显式定义析构函数,编译器会生成默认的
- 多态行为:因为析构函数是 virtual 的,即使通过基类指针删除,也能正确调用实际对象类型的析构函数
4. 验证代码
可以通过以下代码验证析构顺序:
#include <iostream>
class A {
public:
A() { std::cout << "A constructor\n"; }
virtual ~A() { std::cout << "A destructor\n"; }
};
class B : public A {
public:
B() { std::cout << "B constructor\n"; }
~B() override { std::cout << "B destructor\n"; }
};
class C : public B {
public:
C() { std::cout << "C constructor\n"; }
~C() override { std::cout << "C destructor\n"; }
};
int main() {
A* a = new C;
delete a;
}
输出结果:
A constructor
B constructor
C constructor
C destructor
B destructor
A destructor
总结
- 都是虚拟类:A、B、C 都具有多态性(因为继承链中有虚析构函数)
- 析构顺序:C → B → A(从最派生到基类)
- 最佳实践:如果一个类可能被继承,应该声明虚析构函数
补充说明
在您提供的代码中,关于 ~B()
和 ~C()
的虚函数性质,以下是详细分析:
1. ~B()
和 ~C()
都是虚函数
- 继承机制:
当基类A
声明了virtual ~A()
,所有派生类的析构函数自动成为虚函数,即使没有显式写virtual
关键字。 override
关键字的作用:
在~B()
和~C()
中使用的override
只是显式表明它们覆盖了基类的虚函数(是一种良好的编码习惯),但即使去掉override
,它们仍然是虚函数。
2. 验证代码
可以通过以下方式验证其虚函数性质:
#include <iostream>
#include <type_traits>
class A {
public:
virtual ~A() {}
};
class B : public A {
public:
~B() override {}
};
class C : public B {
public:
~C() override {}
};
int main() {
std::cout << std::boolalpha;
std::cout << "Is ~B() virtual? " << std::is_polymorphic<B>::value << "\n"; // true
std::cout << "Is ~C() virtual? " << std::is_polymorphic<C>::value << "\n"; // true
}
输出结果:
Is ~B() virtual? true
Is ~C() virtual? true
3. 关键规则
- 虚析构函数的传递性:
基类的虚析构函数会导致所有派生类的析构函数自动成为虚函数(无论是否显式声明virtual
或override
)。 - 显式声明的好处:
虽然不写virtual
或override
也能保持虚函数性质,但显式声明可以提高代码可读性,并让编译器检查是否真的覆盖了基类虚函数。
4. 代码行为说明
在您的原始代码中:
A* a = new C;
delete a;
由于析构函数是虚函数,调用 delete a
时会正确触发多态析构链:
~C() → ~B() → ~A()
。
如果去掉 virtual
,则只会调用 ~A()
,导致派生类部分内存泄漏。
总结
析构函数 | 是否为虚函数? | 原因 |
---|---|---|
~A() | 是 | 显式声明为 virtual |
~B() | 是 | 继承自 A 的虚析构函数,自动成为虚函数(override 只是显式标注) |
~C() | 是 | 继承自 B (间接继承 A 的虚析构函数),自动成为虚函数 |
最佳实践:始终为基类声明虚析构函数,并在派生类中使用 override
明确意图。