C++ 多态介绍

一些问题:

Q1, 在有继承关系的父子类中,构建和析构一个子类对象时,父子构造函数和析构函数的执行顺序分别是怎样的?

A:

1. 构造和析构顺序

构造函数执行顺序:

  1. 父类构造函数(从最顶层父类开始)
  2. 成员对象的构造函数(按声明顺序)
  3. 子类构造函数

析构函数执行顺序(与构造相反):

  1. 子类析构函数
  2. 成员对象的析构函数(按声明逆序)
  3. 父类析构函数(从最底层父类开始)

代码示例:

复制代码
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           |
+----------------+

问题:

  1. 两份A的成员:D 对象中有两个 A::a

  2. 歧义: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;  // 必须定义
相关推荐
fie88892 小时前
波束赋形MATLAB代码实现
开发语言·matlab
丘狸尾2 小时前
gradio uv无法add
开发语言·python
sali-tec2 小时前
C# 基于halcon的视觉工作流-章67 深度学习-分类
开发语言·图像处理·人工智能·深度学习·算法·计算机视觉·分类
WBluuue2 小时前
Codeforces 1068 Div2(ABCD)
c++·算法
全栈陈序员3 小时前
【Python】基础语法入门(十七)——文件操作与数据持久化:安全读写本地数据
开发语言·人工智能·python·学习
阿沁QWQ3 小时前
C++的map和set
开发语言·c++
武子康3 小时前
Java-193 Spymemcached 深入解析:线程模型、Sharding 与序列化实践全拆解
java·开发语言·redis·缓存·系统架构·memcached·guava
韩凡4 小时前
HashMap的理解与结构
java·开发语言·哈希算法
小猪快跑爱摄影4 小时前
【AutoCad 2025】【C#】零基础教程(二)——遍历 Entity 插件 =》 AutoCAD 核心对象层级结构
开发语言·c#·autocad