一些问题:
Q1, 在有继承关系的父子类中,构建和析构一个子类对象时,父子构造函数和析构函数的执行顺序分别是怎样的?
A:
1. 构造和析构顺序
构造函数执行顺序:
- 父类构造函数(从最顶层父类开始)
- 成员对象的构造函数(按声明顺序)
- 子类构造函数
析构函数执行顺序(与构造相反):
- 子类析构函数
- 成员对象的析构函数(按声明逆序)
- 父类析构函数(从最底层父类开始)
代码示例:
class Base {
public:
Base() { cout << "Base constructor\n"; }
~Base() { cout << "Base destructor\n"; }
};
class Member {
public:
Member() { cout << "Member constructor\n"; }
~Member() { cout << "Member destructor\n"; }
};
class Derived : public Base {
Member m;
public:
Derived() { cout << "Derived constructor\n"; }
~Derived() { cout << "Derived destructor\n"; }
};
// 创建Derived对象时输出:
// Base constructor
// Member constructor
// Derived constructor
// 销毁时输出:
// Derived destructor
// Member destructor
// Base destructor
Q2, 在有继承关系的类体系中,父类的构造函数和析构函数一定要申明为 virtual 吗?如果不申明为 virtual 会怎样?
构造函数:
-
❌ 不能声明为 virtual
-
原因:构造对象时还不知道对象的完整类型
-
语法上不允许:virtual Base() {} 是编译错误
析构函数:
-
✅ 应该声明为 virtual(当有继承时)
-
如果不声明为 virtual:
class Base {
public:
// 非虚析构函数 ❌
~Base() { cout << "Base dtor\n"; }
};class Derived : public Base {
public:
~Derived() { cout << "Derived dtor\n"; }
};int main() {
Base* ptr = new Derived();
delete ptr; // 只调用 Base::~Base()!
// 输出:Base dtor
// ❌ Derived::~Derived() 没有被调用!
// ❌ 内存泄漏(Derived特有成员未释放)
return 0;
}
虚析构函数的正确示例:
class Base {
public:
virtual ~Base() { cout << "Base dtor\n"; } // ✅
};
class Derived : public Base {
public:
~Derived() override { cout << "Derived dtor\n"; }
};
int main() {
Base* ptr = new Derived();
delete ptr; // 正确调用派生类析构函数
// 输出:
// Derived dtor
// Base dtor
return 0;
}
Q3, 什么是 C++ 多态?C++ 多态的实现原理是什么?
什么是多态?
多态 = 同一接口,不同实现
-
编译时多态(静态多态):函数重载、模板
-
运行时多态(动态多态):虚函数
实现原理:
class Shape {
public:
virtual void draw() = 0; // 纯虚函数
virtual ~Shape() {}
};
class Circle : public Shape {
public:
void draw() override { cout << "Drawing Circle\n"; }
};
class Square : public Shape {
public:
void draw() override { cout << "Drawing Square\n"; }
};
int main() {
Shape* shapes[2];
shapes[0] = new Circle();
shapes[1] = new Square();
for (int i = 0; i < 2; i++) {
shapes[i]->draw(); // 同一接口,不同行为
// Circle::draw() 或 Square::draw()
}
// 多态:通过基类指针调用派生类函数
return 0;
}
纯虚函数是 C++ 实现接口和抽象类的关键机制。它的主要用途是定义接口规范,强制派生类实现特定的功能。创建抽象类(不能实例化)
class Animal {
public:
virtual void speak() = 0; // 纯虚函数
};
int main() {
// Animal animal; // 错误:不能创建抽象类的对象
Animal* ptr; // 可以:可以创建抽象类的指针/引用
return 0;
}
强制派生类实现接口
class Animal {
public:
virtual void speak() = 0; // 必须实现
virtual void eat() = 0; // 必须实现
virtual void sleep() { } // 可以选择性重写
};
class Dog : public Animal {
public:
void speak() override { // 必须实现
cout << "Woof!" << endl;
}
void eat() override { // 必须实现
cout << "Eating dog food" << endl;
}
// 可以不实现 sleep(),使用基类的默认实现
};
class BadDog : public Animal {
// 错误:没有实现 speak() 和 eat()
// 这个类仍然是抽象类,不能实例化
};
Q4, 什么是虚函数?虚函数的实现原理是什么?
虚函数表(vtable)机制:
class Base {
public:
virtual void func1() { cout << "Base::func1\n"; }
virtual void func2() { cout << "Base::func2\n"; }
void nonVirtual() { cout << "Base::nonVirtual\n"; }
};
class Derived : public Base {
public:
void func1() override { cout << "Derived::func1\n"; }
// func2 继承 Base::func2
};
内存布局:
Derived 对象内存布局:
+----------------+
| vptr | → 指向 Derived 的虚表
+----------------+
| Base 成员变量 |
+----------------+
| Derived 成员变量|
+----------------+
Derived 虚表:
+----------------+
| &Derived::func1| ← RTTI 信息通常在之前
+----------------+
| &Base::func2 |
+----------------+
Q5,什么是虚表?虚表的内存结构布局如何?虚表的第一项(或第二项)是什么?
虚表内存布局:
// 典型实现(GCC/Clang):
vtable for Derived:
+------------------+
| typeinfo ptr | ← 第一项:RTTI信息(typeinfo)
+------------------+
| offset to top | ← 第二项:到对象顶部的偏移(多重继承时)
+------------------+
| &Derived::func1 | ← 第三项:第一个虚函数
+------------------+
| &Base::func2 | ← 第四项:第二个虚函数
+------------------+
验证代码:
class Base {
public:
virtual void f1() {}
virtual void f2() {}
int x;
};
typedef void (*FuncPtr)();
int main() {
Base b;
// 获取虚表指针(对象内存的第一个字节)
void** vptr = *(void***)&b;
// 第一项:typeinfo(需要RTTI支持)
cout << "First entry: " << vptr[0] << endl;
// 第二项:虚函数指针
FuncPtr f1 = (FuncPtr)vptr[1];
f1(); // 调用 Base::f1
// 第三项:另一个虚函数
FuncPtr f2 = (FuncPtr)vptr[2];
f2(); // 调用 Base::f2
return 0;
}
Q6, 菱形继承与虚表
菱形继承问题:
class A {
public:
virtual void fa() {}
int a;
};
class B : public A {
public:
virtual void fb() {}
int b;
};
class C : public A {
public:
virtual void fc() {}
int c;
};
class D : public B, public C {
public:
virtual void fd() {}
int d;
};
内存布局(没有虚继承):
D 对象内存布局(问题版):
+----------------+
| B::vptr | → B的虚表
+----------------+
| A::a (来自B) |
+----------------+
| B::b |
+----------------+
| C::vptr | → C的虚表
+----------------+
| A::a (来自C) | ← ❌ 重复的A成员!
+----------------+
| C::c |
+----------------+
| D::d |
+----------------+
问题:
-
两份A的成员:D 对象中有两个 A::a
-
歧义:d.a 是哪个?需要 d.B::a 或 d.C::a
解决方案:虚继承
class A {
public:
virtual void fa() {}
int a;
};
class B : virtual public A { // 虚继承
public:
virtual void fb() {}
int b;
};
class C : virtual public A { // 虚继承
public:
virtual void fc() {}
int c;
};
class D : public B, public C {
public:
virtual void fd() {}
int d;
};
内存布局(虚继承):
D 对象内存布局(正确版):
+----------------+
| B::vptr | → B的虚表(包含B的虚函数和虚基类偏移)
+----------------+
| B::b |
+----------------+
| C::vptr | → C的虚表(包含C的虚函数和虚基类偏移)
+----------------+
| C::c |
+----------------+
| D::d |
+----------------+
| A::vptr | → A的虚表(共享部分)
+----------------+
| A::a | ← ❤️ 只有一份A!
+----------------+
虚表布局(GCC实现):
// B的虚表(在D对象中):
vtable for B-in-D:
+------------------+
| offset to top |
+------------------+
| typeinfo for D |
+------------------+
| &B::fb |
+------------------+
| offset to A | ← 指向共享的A部分
+------------------+
| &A::fa |
+------------------+
// C的虚表类似
成员变量 m 的问题:
class A {};
class B : virtual public A { int m; }; // B::m
class C : virtual public A { int m; }; // C::m
class D : public B, public C {};
// D对象中:
// - B::m 在 B 子对象中
// - C::m 在 C 子对象中
// - 不会覆盖,因为位于不同的子对象
// - 访问时需要指定:d.B::m 或 d.C::m
具体场景分析
场景1:工厂模式
class Product {
public:
virtual ~Product() = default; // ✅ 必须virtual
virtual void use() = 0;
};
class ConcreteProduct : public Product {
DatabaseConnection* db; // 需要清理的资源
FileHandle* file;
public:
ConcreteProduct() {
db = new DatabaseConnection();
file = openFile("data.bin");
}
~ConcreteProduct() override { // ✅ 会被正确调用
delete db;
closeFile(file);
}
void use() override { /* ... */ }
};
Product* factory() {
return new ConcreteProduct(); // 返回基类指针
}
int main() {
Product* p = factory();
// ... 使用p ...
delete p; // ✅ 正确调用 ConcreteProduct::~ConcreteProduct()
}
场景2:标准库容器
#include <vector>
#include <memory>
class Resource {
int* data;
public:
Resource() : data(new int[100]) {}
virtual ~Resource() { delete[] data; } // ✅ virtual
};
class SpecialResource : public Resource {
int* extraData;
public:
SpecialResource() : Resource(), extraData(new int[50]) {}
~SpecialResource() override { delete[] extraData; } // ✅
};
int main() {
std::vector<Resource*> resources;
resources.push_back(new Resource());
resources.push_back(new SpecialResource()); // 派生类
// 清理所有资源
for (auto r : resources) {
delete r; // ✅ 正确调用派生类析构函数
}
// 更好的做法:使用智能指针
std::vector<std::unique_ptr<Resource>> safeResources;
safeResources.emplace_back(new SpecialResource());
// unique_ptr 知道正确删除
}
性能考虑
虚析构函数的代价
// 代价1:每个对象多一个虚表指针(通常8字节)
class WithVTable {
virtual ~WithVTable() {} // 有虚表指针
int x;
};
// sizeof(WithVTable) = 16 (8 + 4 + 对齐)
class WithoutVTable {
~WithoutVTable() {} // 无虚表指针
int x;
};
// sizeof(WithoutVTable) = 4
// 代价2:间接调用(通过虚表)
// delete obj; 需要查虚表,比直接调用慢一点
最佳实践
黄金法则
// 规则1:如果一个类可能被继承,就给析构函数加virtual
class Base {
public:
virtual ~Base() = default; // C++11 最佳写法
};
// 规则2:如果类有虚函数,析构函数必须virtual
class Interface {
public:
virtual void method() = 0;
virtual ~Interface() = default; // 必须!
};
// 规则3:抽象基类必须有虚析构函数
class Abstract {
public:
virtual ~Abstract() = 0; // 纯虚析构函数也需要定义!
};
Abstract::~Abstract() = default; // 必须定义