C++ virtual member function FAQ

C++之virtual 方法 1、例子1 #include class A { public: const char* fetchClassName() { return "A"; } }; class B: public A { public: const char* fetchClassName() { return "B"; } }; int main(void) { B obj_b; 阅读详情

【1】  虚成员函数和非虚成员函数调用方式有什么不同?
    非虚成员函数是静态确定的。也就是说,该成员函数(在编译时)被静态地选择,该选择基于指向对象的指针(或引用)的类型。 相比而言,虚成员函数是动态确定的(在运行时)。也就是说,成员函数(在运行时)被动态地选择,该选择基于对象的类型,而不是指向该对象的指针/引用的类型。这被称作“动态绑定/动态联编”。大多数的编译器使用以下的一些的技术,也就是所谓的“VTABLE”机制:
     编译器发现一个类中有被声明为virtual的函数,就会为其搞一个虚函数表,也就是VTABLE。VTABLE实际上是一个函数指针的数组,每个虚函数占用这个数组的一个slot。一个类只有一个VTABLE,不管它有多少个实例。派生类有自己的VTABLE,但是派生类的VTABLE与基类的VTABLE有相同的函数排列顺序,同名的虚函数被放在两个数组的相同位置上。在创建类实例的时候,编译器还会在每个实例的内存布局中增加一个vptr字段,该字段指向本类的VTABLE。通过这些手段,编译器在看到一个虚函数调用的时候,就会将这个调用改写,在分发一个虚函数时,运行时系统跟随对象的 vptr找到类的 vtbl,然后跟随vtbl中适当的项找到方法的代码。
    以上技术的空间开销是存在的:每个对象一个额外的指针(仅仅对于需要动态绑定的对象),加上每个方法一个额外的指针(仅仅对于虚方法)。时间开销也是有的:和普通函数调用比较,虚函数调用需要两个额外的步骤(得到vptr的值,得到方法的地址)。由于编译器在编译时就通过指针类型解决了非虚函数的调用,所以这些开销不会发生在非虚函数上。
    下面代码演示了如何通过获取虚函数指针来调用虚函数的例子:
class Base
{
 int a;
public:
 virtual void fun1() {cout<<"Base::fun1()"<<endl;}
 virtual void fun2() {cout<<"Base::fun2()"<<endl;}
 virtual void fun3() {cout<<"Base::fun3()"<<endl;}
};

class A : public Base
{
 int a;
public:
 virtual void fun1() {cout<<"A::fun1()"<<endl;}
 virtual void fun2() {cout<<"A::fun2()"<<endl;}
};

void *getp (void* p)
{
 return (void*) *(unsigned long*)p;  // 获取对象内存中的虚函数表地址,即vptr指向的内容
}

fun getfun (Base* obj, unsigned long off)
{
 void *vptr = getp(obj);
 
 unsigned char *p = (unsigned char *)vptr;
 p += sizeof(void*)*off;  // 按字节数加上指针偏移,找到存储虚函数地址的内存位置
 
 return (fun)getp(p);     // 去虚函数表中当前位置的内容,即虚函数在内存中的地址
}

int main()
{
 Demo::A a;
 Demo::Base *p = &a;
 
 fun f = getfun(p, 0);
 (*f)();
 f = getfun(p, 1);
 (*f)();
 f = getfun(p, 2);
 (*f)(); 

 return 0;
}
注意:上面示例在vs2003下编译通过,其通过偏移获取虚函数表地址,进而获取虚函数地址,是基于vs下对象的内存布局是先虚函数表指针,然后成员变量的,也就是说指向虚函数表的指针被放置在对象内存的最前面。gcc下的情况有所不同,指向虚函数的指针是放在成员变量后面的。

【2】 析构函数也可以是虚的,甚至是纯虚的,但是构造函数不能是虚的
     纯虚的析构函数并没有什么作用,是虚的就够了。通常只有在希望将一个类变成抽象类(不能实例化的类),而这个类又没有合适的函数可以被纯虚化的时候,可以使用纯虚的析构函数来达到目的。构造函数不能是虚的(为什么?因为在一个构造函数调用期间,虚机制并不工作),但是你可以可能通过虚函数 virtual clone()(对于拷贝构造函数)或虚函数 virtual create()(对于默认构造函数),得到虚构造函数产生的效果。如下:
class Shape {
 public:
   virtual ~Shape() { }                 
// 虚析构函数
   virtual void draw() = 0;             
// 纯虚函数
   virtual void move() = 0;
   
// ...
   virtual Shape* clone()  const = 0;   
// 使用拷贝构造函数
   virtual Shape* create() const = 0;   
// 使用默认构造函数
 };
 
 class Circle : public Shape {
 public:
   Circle* clone()  const { return new Circle(*this); }
   Circle* create() const { return new Circle();      }
   
// ...
 };
    在 clone() 成员函数中,代码 new Circle(*this) 调用 Circle 的拷贝构造函数来复制this的状态到新创建的Circle对象。在 create()成员函数中,代码 new Circle() 调用Circle的默认构造函数。
用户将它们看作“虚构造函数”来使用它们:
 void userCode(Shape& s)
 {
   Shape* s2 = s.clone();
   Shape* s3 = s.create();
   
// ...
   delete s2;    
// 在此处,你可能需要虚析构函数
   delete s3;
 }
    这个函数将正确工作,而不管 Shape 是一个Circle,Square,或是其他种类的 Shape,甚至它们还并不存在。

【3】 构造函数和析构函数中的虚函数调用
    一个类的虚函数在它自己的构造函数和析构函数中被调用的时候,它们就变成普通函数了,不“虚”了。也就是说不能在构造函数和析构函数中让自己“多态”。例如:
class A
{
public:
    A() { foo();}        // 在这里,无论如何都是A::foo()被调用!
    ~A() { foo();}       // 同上
    virtual void foo();
};

class B: public A
{
public:
    virtual void foo();
};

void bar()
{
    A * a = new B;
    delete a;
}
    如果你希望delete a的时候,会导致B::foo()被调用,那么你就错了。同样,在new B的时候,A的构造函数被调用,但是在A的构造函数中,被调用的是A::foo()而不是B::foo()。为什么会有这样的规定呢,原因如下:
    当基类被构造时,对象还不是一个派生类的对象,所以如果 Base::Base()调用了虚函数 virt(),则 Base::virt() 将被调用,即使 Derived::virt()(派生类重写该虚函数)存在。
    同样,当基类被析构时,对象已经不再是一个派生类对象了,所以如果 Base::~Base()调用了virt(),则 Base::virt()得到控制权,而不是重写的 Derived::virt() 。
    当你可以想象到如果 Derived::virt() 涉及到派生类的某个成员对象将造成的灾难的时候,你很快就能看到这种方法的明智。详细来说,如果 Base::Base()调用了虚函数 virt(),这个规则使得 Base::virt()被调用。如果不按照这个规则,Derived::virt()将在派生对象的派生部分被构造之前被调用,此时属于派生对象的派生部分的某个成员对象还没有被构造,而 Derived::virt()却能够访问它。这将是灾难。

【4】私有private的虚函数是否具有多态性?
    考虑下面的例子:
class A
{
public:
    void foo() { bar();}
private:
    virtual void bar() { ...}
};

class B: public A
{
private:
    virtual void bar() { ...}
};
    在这个例子中,虽然bar()在A类中是private的,但是仍然可以出现在派生类中,并仍然可以与public或者protected的虚函数一样产生多态的效果。并不会因为它是private的,就发生A::foo()不能访问B::bar()的情况,也不会发生B::bar()对A::bar()的override不起作用的情况。
    这种写法的语意是:A告诉B,你最好override我的bar()函数,但是你不要管它如何使用,也不要自己调用这个函数。

Copyright@戴维 2006.4  于北京

保姆级避坑指南:在Ubuntu 22.04上用ROS2 Humble搞定TurtleBot3的SLAM与导航(附5个常见报错解决方案) 本文提供了一份详细的Ubuntu 22.04上使用ROS2 Humble和TurtleBot3进行SLAM建图与导航的避坑指南,涵盖Gazebo启动失败、场景加载问题、环境变量设置、GLIBCXX版本冲突及Nav2参数配置等5个常见报错的解决方案,帮助开发者高效完成机器人导航项目。 阅读详情

相关推荐

扩散模型在工业缺陷检测中的应用与优化

扩散模型作为生成式AI的重要分支,通过逐步去噪的逆向过程实现高质量数据生成。其核心U-Net架构结合残差连接和时间步嵌入,特别适合处理工业场景中的细微缺陷检测。在计算机视觉领域,这种技术突破了传统深度学习模型对小样本数据的限制,通过域自适应预训练和小样本微调两阶段框架,显著提升金属表面、纺织品等复杂场景的检测准确率。关键技术实现涉及噪声调度优化、多任务损失设计等工程实践,最终在MVTec AD等工业数据集上达到90%以上的检测精度,为智能制造提供可靠的自动化质检方案。

weixin_30512043的博客 497

C语言参数类型不一致怎么办,C语言 具有不同参数类型的虚函数

我试图理解虚函数是如何工作的,我陷入了某个角色.我写过这个小程序:class First{public:Virtual void f(int a){cout << "First!" << endl;cout << a << endl;}};class Second : public First{public:void f(int a) {cout &...

weixin_36440941的博客 1730

从隐式求解器到显式求解器:COMSOL接触仿真中的求解策略对比与选择

本文深度解析了COMSOL中隐式与显式求解器在接触仿真中的应用与选择策略。针对非线性接触问题的核心挑战,详细对比了两种求解器的数学框架、优化技术及适用场景,并提供了参数调优和调试技巧,帮助工程师高效解决接触仿真中的收敛问题。

weixin_28347369的博客 682

C++类基础(十四)

类的继承——虚函数(一)

文祐的博客 710

人工智能面试总结-分类与聚类

目录说说常用的分类算法,以及各自的优缺点?说说为什么正确率有时不能有效评估分类算法?说说什么样的分类器最好?说说聚类算法有哪些衡量标准?(选看)各聚类算法性能比较汇总说说什么是聚类,你知道哪些聚类算法?具体展开介绍你知道的聚类算法?说说K-Means聚类算法流程?说说K-Means聚类算法如何调优?说说K-Means聚类算法如何选择初始点?说说K-Means聚类聚的是特征还是样本?说说K-Means聚类的特征距离是如何计算的?说说聚类与分类的区别?手撕K-Means聚类 说说常用的分类算法,以及各自的优缺点

啥都生的博客 3863

FPGA入门200例(17):如何阅读RTL原理图(Schematic)?将Verilog代码与门级电路对应

本文针对FPGA开发新手常见误区,重点讲解了RTL原理图的重要性和阅读方法。文章指出,仅通过仿真验证代码会导致板级测试异常,而RTL原理图能直观呈现Verilog代码对应的硬件结构(门电路、寄存器、多路选择器等)。通过"识别基础元件→跟踪信号路径→验证逻辑关系"的三步法,新手可以建立代码与硬件的映射思维,排查冗余逻辑、意外锁存器等错误。文章还结合Vivado/Quartus工具操作指南和典型代码案例,帮助读者掌握从代码到硬件实现的转换过程,避免混淆RTL与门级原理图、忽略信号位宽等常见误

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 199

C++ class

Core concepts of OOP(Object-oriented programming): abstraction, encapsulation, inheritance and polymorphism. Inheritance Syntax Single inheritance: class derived-class:...

weixin_34327761的博客 2239

大语言模型在金融风险预警中的推理应用探索

金融行业一直面临着各种风险,如信用风险、市场风险、操作风险等。准确及时的风险预警对于金融机构的稳定运营和投资者的利益保护至关重要。传统的金融风险预警方法主要基于结构化数据和统计模型,虽然在一定程度上能够识别风险,但对于非结构化数据的处理能力有限,难以捕捉到复杂的风险因素和潜在的风险趋势。大语言模型作为自然语言处理领域的重要突破,具有强大的语言理解和生成能力,能够处理各种形式的文本数据。

AI天才研究院 1169

C++11 override

1,引出原因 class A { public: virtual void add(); virtual void sub(); }; class B : public A { public: void abb(); void sub(); }; 上面的代码可以通过编译,完全没问题,但是类B中的是abb(), 而A中是add(), 类B并没有重写类A中的add()函数,编译器认为是类B自己实现的函数,但是程序员却以为自己重写了,没有意识到自己写错,override修饰符可

qq_43657810的博客 4040

Android AVB 分析(十二)嵌入式设备安全中的 dm-verity 简介

上一篇 《Android AVB 分析(十一)bootloader 是如何进行 verify boot 检查的?》 分析了 bootloader 中是如何调用 libavb 函数库验证 vbmeta 和 boot 等分区的。接下来就打算写一篇介绍 dm-verity 原理的文章,然后再写一篇徒手 dm-verity 实践的文章,结果偶然发现了本篇的英文原版。写得太好,我觉得自己完全没有必要再写原理了。由于原文是英文,有些英语不太好的朋友不一定能完全理解。我花了点时间,结合 AI 将英语原文翻译成中文。

洛奇看世界 3596

图神经网络实战(6)——使用PyTorch构建图神经网络

图数据集通常比简单的连接集合更加丰富,因为节点和边可以具有表示分数、颜色、单词等特征。包含这些额外信息在输入数据中对于生成最佳嵌入至关重要。在本节中,我们将使用 Cora 和 Facebook Page-Page 数据集,首先将它们视为表格数据集,观察香草神经网络在节点特征上的表现如何。然后,尝试在神经网络中加入拓扑信息,实现图神经网络 (Graph Neural Networks, GNN) 架构:一个同时考虑节点特征和边的简单模型。最后,我们将比较两种架构的性能。

盼小辉丶的博客 2万+

lsp.vim 语义高亮与 Inlay Hints:让代码着色和提示更智能

lsp.vim 是一款用 Vim9 script 编写的 Language Server Protocol (LSP) 插件,专为 Vim 9.0 及以上版本打造。今天要重点介绍它的两大"智能"特性:**语义高亮**(Semantic Highlighting)与 **Inlay Hints**(内联提示)。语义高亮让代码着色不再是简单的关键词匹配,而是由语言服务器理解代码含义后精准着色;Inla

gitblog_00652的博客 624

【YOLO26科研级突破】集成BiFPN双向特征金字塔,让多尺度特征融合效率与精度双跃升

打开确保模型解析器能识别BiFPN模块。通过将BiFPN双向特征金字塔集成到YOLO26中,我们实现了一个“全尺度双向特征流动+加权融合+精准检测”的目标检测模型。这个改进不仅在工程上能大幅提升多尺度、复杂场景下的检测精度,更在科研上提供了“双向特征融合”的创新思路——这种思路可以延伸到其他检测网络(如Faster R-CNN、SSD)的Neck模块改进中,帮你在论文中打造独特的研究点。

LXY_LOVE36的博客 200

C++AI编译错误自动修复神器-告别手动Debug的终极方案

在日常 C++ 开发中,编译错误是最频繁遇到的障碍之一。一个中等规模的项目在重构期间,一次 可能产生数十甚至上百条错误信息。这些错误往往具有级联效应——一个头文件的拼写错误可能导致下游十几个翻译单元同时报错,而真正需要修改的地方只有一处。传统的工作流是: 这个循环对于经验丰富的开发者来说尚可接受,但对于以下场景则效率极低:优势:局限:本项目最适合以下场景:阅读

weixin_48053866的博客 280

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Updating -- Python知识

学得快忘得也快,记录一些Python小知识

Rolei的博客 1万+

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Caffe安装问题21_protoc version

1 编译错误 ➜ caffe git:(zxdev_mac) ✗ make No receipt for 'com.apple.pkg.CLTools_Executables' found at '/'. /bin/sh: line 0: [: -gt: unary operator expected /bin/sh: line 0: [: -gt: unary operator expected CXX src/caffe/data_transformer.cpp In file included fr

cv小学dl三年级 707

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Jetson Nano 源码编译安装PySide6

最近要将一个使用PySide6写GUI的程序部署到英伟达Jetson Nano上。原本以为装个conda就能很快装好环境,结果发现arm的生态有点太糟糕了,啥都要自己编译安装,尤其是这个Pyside6。经过几天的探索,终于成功安装,于是写篇教程弥补一下网上对相关内容的缺失。Pyside6不仅源码要自己编译,相关的各种依赖也各种麻烦。下文将我用源码编译安装PySide6的完整历程都写下来了。首先要安装python主要参考连接:https://gitlab.com/-/snippets/2432339。

OTZ_2333的博客 3963

Virtual(虚拟)

Virtual(虚拟):用于修饰方法、属性、索引器或事件声明,并使它们可以在派生类中被重写。调用虚方法时,将为重写成员检查该对象的运行时类型。将调用大部分派生类中的该重写成员,如果没有派生类重写该成员,则它可能是原始成员。默认情况下,方法是非虚拟的。不能重写非虚方法。virtua修饰符不能与static、abstract、private或override修饰符一起使用。除了声明和调用语法不同外

C#菜鸟的专栏 530

C# 虚方法virtual详解

C# 虚方法virtual详解virtual关键字用于声明一个虚方法,可以在派生类中被重写(override)。虚方法允许基类定义一个方法的默认实现,但允许派生类提供自己的实现。1.声明虚方法:在基类中,使用virtual关键字声明一个方法,表示该方法可以在派生类中被重写。publicclassBaseClass { publicvirtualvoidDisplay() ...

dotNET跨平台 440

c++ virtual关键字

在C++中,virtual 是一个关键字,用于实现多态性(polymorphism)和动态绑定(dynamic binding)。它通常与类的成员函数一起使用,以允许在派生类中重写(override)基类的函数,并在运行时根据对象的实际类型调用正确的函数版本。

weixin_47139576的博客 1213

虚方法(virtual)和抽象方法(abstract)的区别

虚方法():通常用于在基类中提供通用实现,但允许子类根据需要进行覆盖,实现特定的行为。抽象方法():使用 关键字在方法声明中标记抽象方法,而且所在的类必须是抽象类(使用 关键字声明的类)或接口(接口中的方法默认为抽象的)。总之,虚方法提供了一种在基类中提供默认实现但允许子类进行覆盖的机制,而抽象方法则强制子类必须提供具体实现。虚方法():可以在基类中提供默认实现,子类可以选择性地覆盖()它,也可以直接使用基类的实现。虚方法():使用 关键字在方法声明中标记虚方法。在C#中,虚方法()和抽象方法()都用于。

厚德载物的博客 871

Virtual Member Functions(虚拟成员函数)

实现模型:每一个class 只会有一个virtual table,内含该class之中有作用的virtual function的地址,每一个object有一个vptr(多继承可能有多个),指向virtual table的所在。 虚函数的地址以及存放虚函数地址的虚表在编译时期就确定下来了,不需要执行期的介入。执行期要做的是,激活虚函数 1.单一继承 单一继承的原理: ①继承基类的虚函数实体:基类所有虚函数实体的地址会被拷贝到derived class的virtual table相对应的slot之中。 ②重写

ZZHinclude的博客 473

C++基础之关键字——virtual详解

virtual C++中的virtual关键字主要有这样几种使用场景:第一,修饰父类中的函数 ;第二,修饰继承性。注意:友元函数、构造函数、static静态函数不能用virtual关键字修饰。普通成员函数和析构函数可以用virtual关键字修饰。 virtual具有继承性:父类中定义为virtual的函数在子类中重写的函数也自动成为虚函数。 修饰父类中的函数 修饰父类中的函数主要分为三种:普通函数、析构函数和纯虚函数。 修饰父类中的普通函数 被修饰的函数称为虚函数, 是C++中多态的一种实现(多说一句,多态

itlilyer的博客 3万+

(学习总结18)C++多态

C++多态讲解

瞌睡不来的博客 1280
上一篇: C++成员函数指针的应用
下一篇: C++对象及多态实现探索之内存布局
sharpdew
博客等级 码龄23年 76粉丝 35原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值