【学会到精通C++】【6】继承的艺术 - 构建类层次结构

学会到精通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. overridefinal (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. 对象构造顺序

  1. 基类构造(按继承声明顺序)
  2. 成员对象构造(按声明顺序)
  3. 派生类构造函数体

2. 对象析构顺序

  1. 派生类析构函数体
  2. 成员对象析构(逆声明顺序)
  3. 基类析构(逆继承顺序)
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. 减少虚函数开销策略

  1. 小类优化:小于64字节的对象更高效
  2. 批量处理:处理对象数组时连续访问
  3. 使用final类:允许编译器去虚拟化
  4. 模板替代:编译期多态(下篇主题)
// 编译期多态示例
template <typename T>
void drawAll(const std::vector<T>& shapes) {
    for (const auto& shape : shapes) {
        shape.draw(); // 静态绑定
    }
}

总结与进阶方向

关键知识点

  1. public继承表达"is-a"关系
  2. 虚函数实现运行时多态
  3. 抽象类定义接口规范
  4. 虚继承解决钻石问题
  5. 构造/析构严格顺序
  6. RTTI实现安全类型转换
  7. 现代override/final语法

常见陷阱

  • 基类缺失虚析构函数(内存泄漏)
  • 切片问题(对象赋值给基类)
  • 覆盖非虚函数(静态绑定)
  • 多重继承接口冲突

下篇预告:第7篇《高级多态技术:模板与CRTP》

  • 函数模板与类模板
  • 模板特化与偏特化
  • 编译期多态
  • 奇异递归模板模式(CRTP)
  • 类型萃取与SFINAE

“继承是C++最强大的特性之一,也是最容易被误用的特性。” —— Scott Meyers

练习任务

  1. 实现几何图形系统并添加三角形支持
  2. 设计带虚继承的类钻石结构
  3. 测量虚函数调用与普通函数性能差异
  4. 实现类型安全的克隆工厂
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值