C++对象构造与析构

文章目录


前言

本文较为详细的研究了C++中包含虚函数继承体系中对象的构造和析构过程。


一、构造函数

cpp 复制代码
class Base
{
public:
    Base() { v_func(); }
    virtual void v_func() 
    {
        printf("base fun\n");
    }
};

class Derived : public Base
{
public:
    Derived() { v_func(); }
    void v_func() override
    {
        printf("derived fun\n");
    }
};

int main()
{
    Derived d;
} 

将输出:

复制代码
base fun
derived fun

解释:

调用Derived构造函数,在初始化列表中隐式调用Base构造函数,在构造函数体中不触发动态绑定,所以调用的是Base::v_func,然后再调用Derived::v_func。

二、详细探究

cpp 复制代码
#include <functional>
#include <stdio.h>

class Base
{
public:
    Base() {  this->v_func(); printf("base vptr:%p\n", *(char**)this); }
    virtual void v_func2() {}
    virtual void v_func() 
    {
        printf("base fun\n");
    }

    virtual ~Base() 
    {
        printf("~Base\n");
    }
};

class Derived : public Base
{
public:
    Derived() { this->v_func(); printf("derived vptr:%p\n", *(char**)this); }
    void v_func() override
    {
        printf("derived fun\n");
    }

    void normal_call_test()
    {
        printf("normal_call_test\n");
    }
    virtual ~Derived()
    {
        printf("~Derived\n");
    }
};

int main()
{
    Base b;
    printf("XXbase vptr: %p\n", *(char**)&b);
    Base b2;
    printf("XXbase vptr: %p\n", *(char**)&b2);

    Derived d;
    printf("%d\n", &Base::v_func2);
    printf("%d\n", &Base::v_func);
    printf("%d\n", &Derived::v_func);

    printf("d vptr: %p\n", *(char**)&d);

    std::bind(&Base::v_func, &d)();
    std::bind(&Derived::v_func, &d)();

}

输出:

复制代码
base fun
base vptr:0x557ff12cf8    # 基类虚表地址
XXbase vptr: 0x557ff12cf8
base fun
base vptr:0x557ff12cf8
XXbase vptr: 0x557ff12cf8
base fun
base vptr:0x557ff12cf8    # 在构造base类部分时vptr指向基类虚表
derived fun
derived vptr:0x557ff12cc8 # 在构造子类部分时vptr又指向了子类虚表
0
8  # 虚函数的地址是个数
8
d vptr: 0x557ff12cc8      # 对象的头8个字节存放该类的虚表指针
derived fun               # 具体调用的函数由对象的实际类型决定
derived fun
~Derived
~Base
~Base
~Base
构造阶段 对象类型 (动态类型) 虚函数表指针 (vptr) 指向 v_func() 调用结果
​​进入 Base 构造函数​​ Base Base 类的虚函数表 (vtable) Base::v_func() (打印 "base fun")
​​进入 Derived 构造函数 ​​ Derived Derived 类的虚函数表 (vtable) Derived::v_func() (打印 "derived fun")
  1. 构建 Derived 对象 d​​:当执行 Derived d; 时,首先调用基类 Base 的构造函数。

  2. ​​在 Base 构造函数中调用 v_func()​​:

    • 此时,Derived 类的部分尚未构造,编译器认为对象的当前类型是 Base。
    • 对象的虚函数表指针(vptr) 此时指向 Base 类的虚函数表。 因此,对 v_func() 的调用会静态绑定到 Base::v_func(),所以第一行输出是 base fun。
  3. ​​进入 Derived 构造函数​​:Base 部分构造完成后,开始构造 Derived 部分。

  4. 在 Derived 构造函数中调用 v_func()​​:

    • 此时,对象的 Base 部分已初始化完成,对象的完整类型已被视为 Derived。
    • 虚函数表指针(vptr)已经指向 Derived 类的虚函数表。 因此,这里的 v_func() 调用会正常使用虚函数机制,动态绑定到 Derived::v_func(),所以输出 derived fun。

总结

  • 避免在构造/析构函数中调用虚函数​​: 正如你的代码所展示的,在构造函数中调用虚函数无法实现多态行为。因为这可能导致未定义行为或错误的结果,所以这被视为一个需要特别注意的不良实践。
  • 析构函数中的类似行为​​: 析构过程中也存在类似情况。当析构一个派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数。在基类的析构函数执行期间,对象会被视为基类类型,任何虚函数调用都会静态绑定到基类的实现。
相关推荐
扶尔魔ocy13 分钟前
【QT C++】常用设计模式
c++·设计模式
晚风予卿云月19 分钟前
【C++高并发内存池】 2.ThreadCache设计
开发语言·c++·高并发内存池·tcmalloc
Dovis(誓平步青云)1 小时前
拖动关键帧角色就抽动,动画编辑器怎样补齐中间状态
开发语言·前端·javascript·机器学习·编辑器
行走的陀螺仪1 小时前
Claude Code 从入门到实战(二):Bash工具、Plan模式与后台任务
开发语言·bash
東隅已逝,桑榆非晚1 小时前
红黑树:从概念到实现
数据结构·c++·笔记·学习
努力努力再努力wz1 小时前
【Lua 入门系列】从执行模型到事件驱动:一文理解 Lua 有栈协程、挂起恢复与协作式调度
开发语言·lua
椒颜皮皮虾྅1 小时前
一套 C# 接口,解锁四类视觉任务:DeploySharp 分类、分割、关键点与旋转框实战
开发语言·分类·c#
2603_965896622 小时前
JavaScript 原型与原型链
开发语言·前端·javascript
艾莉丝努力练剑2 小时前
【AI大模型接入SDK】ChatServer接口测试
网络·c++·人工智能·学习·ai·大模型·sdk
朋友圈自动点赞工具2 小时前
Ruby方法查找:对象不存方法
开发语言·前端·ruby·科技资讯