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;  // 必须定义
相关推荐
wjs20248 分钟前
DOM CDATA
开发语言
Tingjct9 分钟前
【初阶数据结构-二叉树】
c语言·开发语言·数据结构·算法
猷咪36 分钟前
C++基础
开发语言·c++
IT·小灰灰37 分钟前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧39 分钟前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q39 分钟前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳040 分钟前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾40 分钟前
php 对接deepseek
android·开发语言·php
CSDN_RTKLIB43 分钟前
WideCharToMultiByte与T2A
c++
2601_9498683643 分钟前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter