学会到精通C++:继承的艺术 - 构建类层次结构 (6/100)
核心目标:掌握C++继承机制与多态实现,理解虚函数底层原理,解决复杂继承关系中的问题,构建灵活可扩展的类层次结构。
一、继承基础与类型
1. 继承的本质
- 基类 (Base Class):提供通用特性和接口
- 派生类 (Derived Class):继承基类并添加/修改特性
class Shape { // 基类
public:
virtual void draw() const = 0; // 纯虚函数
virtual ~Shape() {} // 虚析构函数
};
class Circle : public Shape { // 派生类
public:
void draw() const override {
std::cout << "Drawing a circle\n";
}
};
2. 继承类型对比
| 继承方式 | 基类public成员 | 基类protected成员 | 基类private成员 |
|---|---|---|---|
| public继承 | 派生类public | 派生类protected | 不可访问 |
| protected继承 | 派生类protected | 派生类protected | 不可访问 |
| private继承 | 派生类private | 派生类private | 不可访问 |
最佳实践:
- 99%场景使用public继承(表示"is-a"关系)
- 避免protected/private继承(优先使用组合)
二、多态与虚函数机制
1. 静态绑定 vs 动态绑定
Shape* shape = new Circle();
shape->draw(); // 动态绑定 - 调用Circle::draw()
delete shape;
2. 虚函数表(vtable)原理
- 每个含虚函数的类有vtable
- 对象隐含vptr指向vtable
- 调用虚函数时通过vptr查找函数地址
3. override 与 final (C++11)
class AdvancedCircle : public Circle {
public:
void draw() const override { // 显式声明覆盖
std::cout << "Drawing advanced circle\n";
}
// final函数:禁止进一步覆盖
virtual void serialize() const final {}
};
// final类:禁止继承
class NonInheritable final {};
三、抽象类与接口设计
1. 纯虚函数与抽象类
class Serializable {
public:
virtual std::string toJSON() const = 0; // 纯虚函数
virtual ~Serializable() = default;
};
// 抽象类:包含至少一个纯虚函数
class Shape : public Serializable {
// ...
};
2. 接口类设计模式
class IClonable {
public:
virtual IClonable* clone() const = 0;
virtual ~IClonable() = default;
};
class Document : public IClonable {
public:
Document* clone() const override {
return new Document(*this);
}
};
四、多重继承与钻石问题
1. 多重继承基本用法
class InputDevice {
public:
virtual void poll() = 0;
};
class OutputDevice {
public:
virtual void write() = 0;
};
class IOController : public InputDevice, public OutputDevice {
public:
void poll() override { /*...*/ }
void write() override { /*...*/ }
};
2. 虚继承解决钻石问题
Base
/ \
/ \
Derived1 Derived2
\ /
\ /
Final
class Base {
public:
int data;
};
class Derived1 : virtual public Base {}; // 虚继承
class Derived2 : virtual public Base {}; // 虚继承
class Final : public Derived1, public Derived2 {
public:
void setData(int val) {
data = val; // 明确唯一副本
}
};
五、构造与析构顺序
1. 对象构造顺序
- 基类构造(按继承声明顺序)
- 成员对象构造(按声明顺序)
- 派生类构造函数体
2. 对象析构顺序
- 派生类析构函数体
- 成员对象析构(逆声明顺序)
- 基类析构(逆继承顺序)
class Base {
public:
Base() { std::cout << "Base constructor\n"; }
~Base() { std::cout << "Base destructor\n"; }
};
class Member {
public:
Member() { std::cout << "Member constructor\n"; }
~Member() { std::cout << "Member destructor\n"; }
};
class Derived : public Base {
Member m;
public:
Derived() { std::cout << "Derived constructor\n"; }
~Derived() { std::cout << "Derived destructor\n"; }
};
// 使用:
// Derived d; 输出顺序:
// Base constructor
// Member constructor
// Derived constructor
// Derived destructor
// Member destructor
// Base destructor
六、运行时类型识别(RTTI)
1. dynamic_cast 安全向下转型
Shape* shape = new Circle();
// 安全向下转型
if (Circle* circle = dynamic_cast<Circle*>(shape)) {
circle->draw();
} else {
// 处理非Circle类型
}
2. typeid 运算符
#include <typeinfo>
void printType(Shape* shape) {
if (typeid(*shape) == typeid(Circle)) {
std::cout << "Circle object\n";
}
std::cout << typeid(*shape).name() << "\n";
}
七、现代C++继承特性
1. 继承构造函数 (C++11)
class Base {
public:
Base(int, double) {}
};
class Derived : public Base {
public:
using Base::Base; // 继承基类构造函数
};
2. 委托继承构造函数
class Derived : public Base {
public:
Derived() : Derived(0, 0.0) {}
Derived(int x, double y) : Base(x, y) {}
};
3. 显式重写控制 (C++11)
class Base {
public:
virtual void foo() {}
virtual void bar() = 0;
};
class Derived : public Base {
public:
void foo() override {} // 正确重写
// 编译错误:未重写纯虚函数
// void bar(int) override {}
};
八、设计原则与最佳实践
1. Liskov替换原则(LSP)
- 派生类对象必须能替换基类对象
- 不改变基类契约(前置/后置条件)
- 示例:正方形不应继承长方形
2. 组合优于继承
// 错误继承
class Stack : public Vector { /*...*/ };
// 正确组合
class Stack {
private:
std::vector<int> elements; // 组合
public:
void push(int val) { elements.push_back(val); }
void pop() { elements.pop_back(); }
};
3. 避免深层次继承
- 推荐:不超过3层继承深度
- 复杂系统使用组合+接口
九、实战:几何图形系统
class Shape {
public:
virtual double area() const = 0;
virtual void draw() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override {
return 3.14159 * radius * radius;
}
void draw() const override { /*...*/ }
};
class Rectangle : public Shape {
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
void draw() const override { /*...*/ }
};
// 工厂函数
std::unique_ptr<Shape> createShape(ShapeType type) {
switch(type) {
case CIRCLE: return std::make_unique<Circle>(1.0);
case RECTANGLE: return std::make_unique<Rectangle>(1.0, 2.0);
default: throw std::invalid_argument("Unknown shape");
}
}
十、性能考量与优化
1. 虚函数开销来源
| 操作 | 开销周期 | 说明 |
|---|---|---|
| vptr访问 | 1-3周期 | 读取对象头部 |
| 间接调用 | 1-5周期 | 通过函数指针调用 |
| 缓存未命中 | 10-100+周期 | vtable不在缓存中 |
2. 减少虚函数开销策略
- 小类优化:小于64字节的对象更高效
- 批量处理:处理对象数组时连续访问
- 使用final类:允许编译器去虚拟化
- 模板替代:编译期多态(下篇主题)
// 编译期多态示例
template <typename T>
void drawAll(const std::vector<T>& shapes) {
for (const auto& shape : shapes) {
shape.draw(); // 静态绑定
}
}
总结与进阶方向
关键知识点:
- public继承表达"is-a"关系
- 虚函数实现运行时多态
- 抽象类定义接口规范
- 虚继承解决钻石问题
- 构造/析构严格顺序
- RTTI实现安全类型转换
- 现代override/final语法
常见陷阱:
- 基类缺失虚析构函数(内存泄漏)
- 切片问题(对象赋值给基类)
- 覆盖非虚函数(静态绑定)
- 多重继承接口冲突
下篇预告:第7篇《高级多态技术:模板与CRTP》
- 函数模板与类模板
- 模板特化与偏特化
- 编译期多态
- 奇异递归模板模式(CRTP)
- 类型萃取与SFINAE
“继承是C++最强大的特性之一,也是最容易被误用的特性。” —— Scott Meyers
练习任务:
- 实现几何图形系统并添加三角形支持
- 设计带虚继承的类钻石结构
- 测量虚函数调用与普通函数性能差异
- 实现类型安全的克隆工厂
11万+

被折叠的 条评论
为什么被折叠?



