【c++】多态中的构造函数和析构函数

devtools/2025/2/12 9:55:05/

【c++】多态中的构造函数和析构函数

一、构造函数

1. 构造函数的核心任务

1.1构造函数负责 初始化对象的成员变量设置虚表指针(vptr)
  • 虚表指针(vptr):当一个类包含虚函数时,编译器会隐式地为该类的每个对象插入一个指向虚表(vtable)的指针(vptr)。
  • 虚表(vtable):存储该类所有虚函数地址的静态表,在编译时生成,每个类唯一。
1.2构造函数负责
  • 1创建对象:构造函数在对象被实例化时自动调用,负责分配内存。
  • 2初始化成员:通过初始化列表或函数体为对象的成员变量赋初始值。
  • 3支持隐式类型转换:若构造函数只有一个参数(或其余参数有默认值),可隐式转换参数类型为类类型。
2. 构造函数的执行顺序

对象构造按 基类 → 派生类 的顺序进行,vptr 的初始化也遵循这一规则:

  1. 基类构造阶段:初始化基类成员,vptr 指向基类的虚表。
  2. 派生类构造阶段:初始化派生类成员,vptr 被更新为指向派生类的虚表。
    ps:构建派生类对象时首先构造匿名的基类对象(前面提到过)此时调用基类的构造函数,使得虚函数指针指向基类的虚函数表,接着调用派生类的构造函数,重置虚表指针指向派生类的虚表
class Base {
public:Base() { // 此时 vptr 指向 Base 的虚表}virtual void func() { /*...*/ }
};class Derived : public Base {
public:Derived() : Base() { // 此时 vptr 更新为指向 Derived 的虚表}void func() override { /*...*/ }
};
3. 为什么构造函数不能是虚函数?
  • 对象未完全构造:在构造函数执行时,对象尚未完全构造,vptr 可能未正确初始化。
  • 静态绑定:构造函数调用在编译时确定类型(如 new Derived())属于静态联编
  • 逻辑矛盾:虚函数依赖 vptr,但 vptr 在构造函数中初始化,形成循环依赖。
  • 构造函数调用是编译时确定,如果为虚构造函数,编译器无法确认你够简单类型

二、析构函数与虚析构函数

1. 析构函数的核心任务

析构函数负责 释放对象资源重置 vptr(编译器隐式处理)。

2. 虚析构函数的重要性

若基类指针指向派生类对象,派生类在堆区分配空间非虚析构函数会导致内存泄漏

class Base {
public:~Base() { cout << "Base destroyed" << endl; }  // 非虚析构函数
};class Derived : public Base {
public:~Derived() { cout << "Derived destroyed" << endl; }
};Base* obj = new Derived();
delete obj;  // 仅调用 Base 的析构函数,Derived 的析构函数未执行!

在delete obj指针时 这里使用的是静态联编的方式 根据obj类型直接调用了base析构函数

解决方法:将基类析构函数声明为虚函数

virtual ~Base() { ... }

此时 delete obj 会触发动态绑定,正确调用 Derived::~Derived()

3. 虚析构函数的执行顺序

析构按 派生类 → 基类 顺序执行:

  1. 派生类析构函数执行,vptr 指向派生类的虚表。
  2. 基类析构函数执行,vptr 被重置为指向基类的虚表

三、关键点总结

特性构造函数析构函数
能否为虚函数❌ 不能✅ 必须为虚(若可能被继承)
vptr 初始化/重置初始化(指向当前类的虚表)重置(指向当前类的虚表)
多态行为静态绑定(编译时确定类型)动态绑定(需虚析构函数支持)
资源管理分配资源(如内存、文件句柄)释放资源

四、代码示例:完整生命周期

#include <iostream>
using namespace std;class Animal {
public:Animal() { cout << "Animal constructed" << endl; }virtual ~Animal() { cout << "Animal destroyed" << endl; }  // 虚析构函数virtual void speak() { cout << "Animal sound" << endl; }
};class Dog : public Animal {
public:Dog() { cout << "Dog constructed" << endl; }~Dog() override { cout << "Dog destroyed" << endl; }void speak() override { cout << "Woof!" << endl; }
};int main() {Animal* animal = new Dog();  // 构造顺序:Animal → Doganimal->speak();             // 动态绑定,输出 "Woof!"delete animal;               // 析构顺序:Dog → Animalreturn 0;
}

输出

Animal constructed
Dog constructed
Woof!
Dog destroyed
Animal destroyed

http://www.ppmy.cn/devtools/157615.html

相关文章

使用Redis解决使用Session登录带来的共享问题

在学习项目的过程中遇到了使用Session实现登录功能所带来的共享问题&#xff0c;此问题可以使用Redis来解决&#xff0c;也即是加上一层来解决问题。 接下来介绍一些Session的相关内容并且采用Session实现登录功能&#xff08;并附上代码&#xff09;&#xff0c;进行分析其存在…

Yolo图片标注的一些问题

1.标注工具的选择 在img.net和瑞芯微的双重加持下&#xff0c;现在的计算机视觉识别已经在各行业快速推进。进行自行标注时&#xff0c;首先遇到的问题就是标注工具的选择问题&#xff0c;标注作业不需要自己手工完成——也没有必要。类似这样的通用需求&#xff0c;交给专业…

计算机视觉语义分割——Attention U-Net(Learning Where to Look for the Pancreas)

计算机视觉语义分割——Attention U-Net(Learning Where to Look for the Pancreas) 文章目录 计算机视觉语义分割——Attention U-Net(Learning Where to Look for the Pancreas)摘要Abstract一、Attention U-Net1. 基本思想2. Attention Gate模块3. 软注意力与硬注意力4. 实验…

kubernetes 集群命令行工具 kubectl

一、kubectl 简介 kubectl 是 Kubernetes 的命令行工具&#xff0c;用于与 Kubernetes 集群进行通信。它发送命令到 Kubernetes API 服务器&#xff0c;然后 API 服务器将这些命令应用到集群中。kubectl 可以执行多种操作&#xff0c;如创建、更新、删除和管理集群中的资源。 …

【前端】【面试】【经典一道题】前端 Vue、React 采用单向数据流的原因

前端Vue、React采用单向数据流的原因 一、可预测性 1. 数据流向清晰 在单向数据流架构里&#xff0c;数据从父组件流向子组件的路径是明确且可预期的。 React示例&#xff1a;父组件通过 props 传递数据给子组件&#xff0c;子组件只能读取 props 中的数据&#xff0c;没有直…

路由器如何进行数据包转发?

路由器进行数据包转发的过程是网络通信的核心之一&#xff0c;主要涉及以下几个步骤&#xff1a; 接收数据包&#xff1a;当一个数据包到达路由器的一个接口时&#xff0c;它首先被暂时存储在该接口的缓冲区中。 解析目标地址&#xff1a;路由器会检查数据包中的目标IP地址。…

新装windows系统配置

安装windows 将windows镜像iso工具刻录到u盘里。开机选择u盘启动&安装激活。Win10专业版用户请在命令提示符窗口中依次输入&#xff1a;slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GXslmgr /skms kms.03k.orgslmgr /ato系统安装完成后&#xff0c;可以到https://www.microsof…

DeepSeek时代:百度们亟需“深度求索”

文&#xff1a;互联网江湖 作者&#xff1a;刘致呈 眼看着梁文峰被捧上中国AI神坛&#xff0c;科技巨头们的心情一定是复杂的。 就像大刘笔下的《三体》中&#xff0c;当三百年后的人类太空舰队&#xff0c;面对水滴探测器时是五味杂陈的。 当科技大佬们纷纷断言&#xff0c;…