C++中构造函数中调用虚函数的问题
C++基抽象类的构造析构(纯)虚函数
![C++基抽象类的构造析构(纯)虚函数](https://img.taocdn.com/s3/m/24686c888662caaedd3383c4bb4cf7ec4afeb6be.png)
C++基抽象类的构造析构(纯)虚函数⼀、析构函数可定义为⼀、析构函数可定义为纯虚函数纯虚函数,但也必须给出函数定义,但也必须给出函数定义 Effective C++ 条歀07: 为多态基类声明virtual 析构函数(Declare destructors virtual in polymorphic base classes ) 在某些类⾥声明纯虚析构函数很⽅便。
纯虚函数将产⽣抽象类——不能实例化的类(即不能创建此类型的对象)。
有些时候,你想使⼀个类成为抽象类,但刚好⼜没有任何纯虚函数。
怎么办?因为抽象类是准备被⽤做基类的,基类必须要有⼀个虚析构函数,纯虚函数会产⽣抽象类,所以⽅法很简单:在想要成为抽象类的类⾥声明⼀个纯虚析构函数。
1 //这⾥是⼀个例⼦:2 class awov {3 public :4 virtual ~awov() = 0; // 声明⼀个纯虚析构函数5 }; 这个类有⼀个纯虚函数,所以它是抽象的,⽽且它有⼀个虚析构函数,所以不会产⽣析构函数问题。
但这⾥还有⼀件事:必须提供纯虚析构函数的定义: awov::~awov() { ... } // 纯虚析构函数的定义 这个定义是必需的,因为虚析构函数⼯作的⽅式是:最底层的派⽣类的析构函数最先被调⽤,然后各个基类的析构函数被调⽤。
这就是说,即使是抽象类,编译器也要产⽣对~awov 的调⽤,所以要保证为它提供函数体。
如果不这么做,链接器就会检测出来,最后还是得回去把它添上。
⼆、? 关于C++为什么不⽀持虚拟构造函数,Bjarne 很早以前就在C++Style and Technique FAQ ⾥⾯做过回答 Avirtual call is a mechanism to get work done given partialinformation. In particular, "virtual" allows us to call afunction knowing only an interfaces and not the exact type of theobject. To create an object you need complete information.Inparticular, you need to know the exact type of what you want tocreate. Consequently, a "call to a constructor" cannot bevirtual. 含义⼤概是这样的:虚函数调⽤是在部分信息下完成⼯作的机制,允许我们只知道接⼝⽽不知道对象的确切类型。
C++语言中的虚函数研究
![C++语言中的虚函数研究](https://img.taocdn.com/s3/m/0abe694469eae009581becb3.png)
万方数据 万方数据 万方数据C++语言中的虚函数研究作者:徐启丰, 胡勇, 万玉成, XU Qifeng, HU Yong, WANG Yucheng作者单位:徐州空军学院,江苏,徐州,221000刊名:现代电子技术英文刊名:MODERN ELECTRONICS TECHNIQUE年,卷(期):2010,33(4)参考文献(14条)1.Stanley B Lippman;侯捷Inside the C++ Object Model 20012.蓝雯飞;陆际光C++面向对象程序设计中的多态性研究[期刊论文]-计算机工程与应用 2000(08)3.Bjarne Stroustrup;裘宗燕C++的设计与演化 20024.赵红超;方金云;唐志敏C++的动态多态和静态多态[期刊论文]-计算机工程 2005(20)5.蓝雯飞C++中的多态性及其应用 1998(07)6.袁亚丽;肖桂云C++中虚函数的实现技术研究[期刊论文]-河北北方学院学报(自然科学版) 2006(05)7.Scott Mayers More Effective C++ 19968.和力;吴丽贤关于C++虚函数底层实现机制的研究与分析[期刊论文]-计算机工程与设计 2008(10)9.亚鹏关于C++中虚函数的几个问题 2006(02)10.Terrence W Pratt;傅育熙程序设计语言:设计与实现 200111.张昀C++中的多态性研究 2009(02)12.Bjarne Stroustrup The C++ Programming Language 200113.夏承遗;董玉涛;赵德新C++中虚函数的实现机制[期刊论文]-天津理工学院学报 2004(03)14.蓝雯飞C++语言中的面向对象特征探讨[期刊论文]-计算机工程与应用 2000(09)本文链接:/Periodical_xddzjs201004048.aspx。
C++中虚函数工作原理和(虚)继承类的内存占用大小计算
![C++中虚函数工作原理和(虚)继承类的内存占用大小计算](https://img.taocdn.com/s3/m/965bd71f5727a5e9856a613d.png)
C++中虚函数工作原理和(虚)继承类的内存占用大小计算一、虚函数的工作原理虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。
典型情况下,这一信息具有一种被称为vptr(virtual table pointer,虚函数表指针)的指针的形式。
vptr 指向一个被称为vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到vtbl。
当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的vptr 指向的vtbl,然后在vtbl 中寻找合适的函数指针。
虚拟函数的地址翻译取决于对象的内存地址,而不取决于数据类型(编译器对函数调用的合法性检查取决于数据类型)。
如果类定义了虚函数,该类及其派生类就要生成一张虚拟函数表,即vtable。
而在类的对象地址空间中存储一个该虚表的入口,占4个字节,这个入口地址是在构造对象时由编译器写入的。
所以,由于对象的内存空间包含了虚表入口,编译器能够由这个入口找到恰当的虚函数,这个函数的地址不再由数据类型决定了。
故对于一个父类的对象指针,调用虚拟函数,如果给他赋父类对象的指针,那么他就调用父类中的函数,如果给他赋子类对象的指针,他就调用子类中的函数(取决于对象的内存地址)。
虚函数需要注意的大概就是这些个地方了,之前在More effective C++上好像也有见过,不过这次在Visual C++权威剖析这本书中有了更直白的认识,这本书名字很牛逼,看看内容也就那么回事,感觉名不副实,不过说起来也是有其独到之处的,否则也没必要出这种书了。
每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。
C试题及答案
![C试题及答案](https://img.taocdn.com/s3/m/33b7b639f46527d3240ce0ad.png)
C++程序设计模拟试卷(五)一、单项选择题(本大题共20小题,每小题1分,共20分)在每小题列出的四个备选项中只有一个是符合题目要求的,请将其代码填写在题后的括号内。
错选、多选或未选均无分。
1.静态成员函数没有()A.返回值B.this指针C.指针参数D.返回类型答案:B解析:静态成员函数是普通的函数前加入static,它具有函数的所有的特征:返回类型、形参,所以使用静态成员函数,指针可以作为形参,也具有返回值。
静态成员是类具有的属性,不是对象的特征,而this表示的是隐藏的对象的指针,因此静态成员函数没有this指针。
静态成员函数当在类外定义时,要注意不能使用static关键字作为前缀。
由于静态成员函数在类中只有一个拷贝(副本),因此它访问对象的成员时要受到一些限制:静态成员函数可以直接访问类中说明的静态成员,但不能直接访问类中说明的非静态成员;若要访问非静态成员时,必须通过参数传递的方式得到相应的对象,再通过对象来访问。
2.在类的定义中,用于为对象分配内存空间,对类的数据成员进行初始化并执行其他内部管理操作的函数是()A.友元函数B.虚函数C.构造函数D.析构函数答案:C解析:定义构造函数作用就是初始化对象,而析构函数释放对象空间。
虚函数用于完成多态性,友元增加访问方便性。
3.所有在函数中定义的变量,都是()A.全局变量B.局部变量C.静态变量D.寄存器变量答案:B解析:变量存储类可分为两类:全局变量和局部变量。
(1)全局变量:在函数外部定义的变量称为全局变量,其作用域为:从定义变量的位置开始到源程序结束。
全局变量增加了函数之间数据联系的渠道,全局变量作用域内的函数,均可使用、修改该全局变量的值,但是使用全局变量降低了程序的可理解性,软件工程学提倡尽量避免使用全局变量。
(2)局部变量:在函数内部定义的变量称为局部变量,其作用域为:从定义变量的位置开始到函数结束。
局部变量包含自动变量(auto)静态变量(static)以及函数参数。
C++_虚构造函数和虚析构函数
![C++_虚构造函数和虚析构函数](https://img.taocdn.com/s3/m/589da0c758f5f61fb7366672.png)
delete pf; //OK, ultimately invokes File::~File()
在某些情况下定义其它纯虚成员函数可能也是非常有用的(比如说在调试应用程序以及记录应用程序的日志时)。例如,在一个不应该被调用,但是由于一个缺陷而被调用的基类中,如果有一个纯虚成员函数,那么我们可以为它提供一个定义。
return 3;
}
};
class Wrap
{
public:
Wrap(string key){
if(key=="a") m_obj= new A(key);
if(key=="b") m_obj= new B(key);
{
Shape* s2=s.clone();
Shape* s3=s.create();
// ...
delete s2; //在此处,你可能需要虚析构函数
delete s3;
}
这个函数将正确工作,而不管Shape是一个Circle,Square,或是其他种类的Shape,甚至它们还并不存在。
纯虚成员函数通常没有定义;它们是在抽象类中声明,然后在派生类中实现。比如说下面的例子:
class File //an abstract class
{
public:
virtual int open(const string & path, int mode=0x666)=0;
virtual int close()=0;
}
这样,我们就可以记录所有对纯虚函数的调用,并且还可以定位错误代码;不为纯虚函数提供定义将会导致整个程序无条件地终止。
构造函数不能为虚函数的理由
![构造函数不能为虚函数的理由](https://img.taocdn.com/s3/m/76d83e254b35eefdc8d3330c.png)
一、构造函数不能为虚函数的理由:1,从存储空间角度虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。
问题出来了,如果构造函数是虚的,就需要通过vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
2,从使用角度虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。
构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。
所以构造函数没有必要是虚函数。
虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。
而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
3、构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过实验室的基类的指针或引用去访问它但析构却不一定,我们往往通过基类的指针来销毁对象。
这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。
4、从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有太大的必要成为虚函数5、当一个构造函数被调用时,它做的首要的事情之一是初始化它的V P T R。
因此,它只能知道它是“当前”类的,而完全忽视这个对象后面是否还有继承者。
当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码- -既不是为基类,也不是为它的派生类(因为类不知道谁继承它)。
所以它使用的V P T R必须是对于这个类的V TA B L E。
而且,只要它是最后的构造函数调用,那么在这个对象的生命期内,V P T R将保持被初始化为指向这个V TA B L E, 但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置V P T R指向它的V TA B L E,等.直到最后的构造函数结束。
构造函数和析构函数中可以调用调用虚函数吗
![构造函数和析构函数中可以调用调用虚函数吗](https://img.taocdn.com/s3/m/b9622db3970590c69ec3d5bbfd0a79563c1ed40f.png)
构造函数和析构函数中可以调⽤调⽤虚函数吗可以,虚函数底层实现原理(但是最好不要在构造和析构函数中调⽤) 可以,但是没有动态绑定的效果,⽗类构造函数中调⽤的仍然是⽗类版本的函数,⼦类中调⽤的仍然是⼦类版本的函数。
effictive c++第九条,绝不在构造和析构过程中调⽤virtual,因为构造函数中的base的虚函数不会下降到derived上。
⽽是直接调⽤base类的虚函数。
绝不在构造和析构函数中调⽤virtual函数:a) 如果有继承,构造函数会先调⽤⽗类构造函数,⽽如果构造函数中有虚函数,此时⼦类还没有构造,所以此时的对象还是⽗类的,不会触发多态。
更容易记的是基类构造期间,virtual函数不是virtual函数。
b) 析构函数也是⼀样,⼦类先进⾏析构,这时,如果有virtual函数的话,⼦类的内容已经被析构了,C++会视其⽗类,执⾏⽗类的virtual函数。
c) 总之,在构造和析构函数中,不要⽤虚函数。
如果必须⽤,那么分离出⼀个Init函数和⼀个close函数,实现相关功能即可。
原⽂链接:https:///chen134225/article/details/81564972第⼀个理由是概念上的。
在概念上,构造函数的⼯作是⽣成⼀个对象。
在任何构造函数中,可能只是部分形成对象——我们只能知道基类已被初始化,但并不能知道哪个类是从这个基类继承来的。
然⽽,虚函数在继承层次上是“向前”和“向外”进⾏调⽤。
它可以调⽤在派⽣类中的函数。
如果我们在构造函数中也这样做,那么我们所调⽤的函数可能操作还没有被初始化的成员,这将导致灾难发⽣。
第⼆个理由是机械上的。
当⼀个构造函数被调⽤时,它做的⾸要的事情之⼀就是初始化它的VPTR。
然⽽,它只能知道它属于“当前”类——即构造函数所在的类。
于是它完全不知道这个对象是否是基于其它类。
当编译器为这个构造函数产⽣代码时,它是为这个类的构造函数产⽣代码——既不是为基类,也不是为它的派⽣类(因为类不知道谁继承它)。
C++试题及答案 (五)
![C++试题及答案 (五)](https://img.taocdn.com/s3/m/5d6473d1a5e9856a57126054.png)
C++程序设计模拟试卷(五)一、单项选择题(本大题共20小题,每小题1分,共20分)在每小题列出的四个备选项中只有一个是符合题目要求的,请将其代码填写在题后的括号内。
错选、多选或未选均无分。
1。
静态成员函数没有()A。
返回值B. this指针C. 指针参数D。
返回类型答案:B解析:静态成员函数是普通的函数前加入static,它具有函数的所有的特征:返回类型、形参,所以使用静态成员函数,指针可以作为形参,也具有返回值。
静态成员是类具有的属性,不是对象的特征,而this表示的是隐藏的对象的指针,因此静态成员函数没有this 指针。
静态成员函数当在类外定义时,要注意不能使用static关键字作为前缀.由于静态成员函数在类中只有一个拷贝(副本),因此它访问对象的成员时要受到一些限制:静态成员函数可以直接访问类中说明的静态成员,但不能直接访问类中说明的非静态成员;若要访问非静态成员时,必须通过参数传递的方式得到相应的对象,再通过对象来访问.2. 在类的定义中,用于为对象分配内存空间,对类的数据成员进行初始化并执行其他内部管理操作的函数是()A. 友元函数B。
虚函数C. 构造函数D. 析构函数答案:C解析:定义构造函数作用就是初始化对象,而析构函数释放对象空间。
虚函数用于完成多态性,友元增加访问方便性。
3。
所有在函数中定义的变量,都是()A. 全局变量B. 局部变量C。
静态变量D. 寄存器变量答案:B解析:变量存储类可分为两类:全局变量和局部变量.(1)全局变量:在函数外部定义的变量称为全局变量,其作用域为:从定义变量的位置开始到源程序结束。
全局变量增加了函数之间数据联系的渠道,全局变量作用域内的函数,均可使用、修改该全局变量的值,但是使用全局变量降低了程序的可理解性,软件工程学提倡尽量避免使用全局变量。
(2)局部变量:在函数内部定义的变量称为局部变量,其作用域为:从定义变量的位置开始到函数结束。
局部变量包含自动变量(auto)静态变量(static)以及函数参数.auto变量意味着变量的存储空间的分配与释放是自动进行的.说明符auto可以省略。
C++经典面试题(2012年校园招聘)
![C++经典面试题(2012年校园招聘)](https://img.taocdn.com/s3/m/b10b152dbd64783e09122ba8.png)
1、在C++程序中调用被C编译器编译后的函数,为什么要加extern "C"?答案:C语言不支持函数重载,C++ 提供了C连接交换制定符号extern "C"解决名字匹配问题。
2、如何判断一段程序是由C编译程序还是由C++编译程序编译的?答案:C++编译时定义了_cplusplus。
C编译时定义了_STDC_。
3、main主函数执行完毕后,是否可能会再执行一段代码?给出说明。
答案:如果需要加入一段在main退出后执行的代码,可以使用atexit()函数注册一个函数,代码为:int atexit(void (*funciton)(void));4、用预处理指令#define声明一个常数,用以表明一年中有多少秒(忽略闰年问题)。
答案:#define SECONDS_PER_YEAR (60*60*24*365)UL 注意括号,同时不能用分号,不要写出计算出来的实际值,用到无符号长整型。
5、const与#define有什么区别?答案:两者都可以定义常量。
1)const常量有数据类型,而宏常量没有数据类型。
编译器可以对前者进行安全检查,而对后者只进行字符替换,没有类型安全检查;2)有些集成化调试工具可以对const进行调试,不能对宏常量进行调试。
const在C中其实是值不能修改的变量,因此会给它分配存储空间(默认是外部连接的),在C++中对于基本数据类型的常量,编译器会把它放到符号表里而不是分配存储空间(默认是内部连接的,若强制声明为extern,则需要分配存储空间)。
const int bufsize = 100; //在C++中注意此处int一定不可少,因为c++中不支持默认int型char buf[bufsize]; //在C++中这样写没问题const bufsize;//在C中这样写没问题,但是在C++中是有错误的。
//要做同样的事情,C++需改强制声明为extern:extern const bufsize;6、写一个标准宏MIN,输入两个参数返回最小值。
C++网络作业6答案
![C++网络作业6答案](https://img.taocdn.com/s3/m/2ebf3521852458fb770b56df.png)
作业4一、选择题1.下列关于动态联编的描述中,错误的是_________。
DA)动态联编是以虚函数为基础的B)动态联编是在运行时确定所调用的函数代码的C)动态联编调用函数操作是指向对象的指针或对象引用D)动态联编是在编译时确定操作函数的注:先期联编也称静态联编,迟后联编也称动态联编。
注释:动态联编一直要到程序运行时才能确定调用哪个函数。
虚函数是实现动态联编的必要条件之一。
没有虚函数一定不能实现动态联编,但有虚函数存在时,必须同时满足下列条件,才能够实现动态联编:●类之间满足子类型关系;●调用虚函数操作的是指向对象的指针或者对象引用:或者是由成员函数调用虚函数。
2 关于虚函数的描述中,正确的是________。
DA)虚函数是一个静态成员函数B)虚函数是一个非成员函数C)虚函数既可以在函数说明时定义,也可以在函数实现时定义D)派生类的虚函数与基类中对应的虚函数具有相同的参数个数和类型注释:虚函数是非静态的成员函数。
它不能是友元函数,但可以在另一个类中被声明为友元函数。
虚函数声明只能出现在类定义的函数原型声明中,而不能在成员函数的函数体实现的时候。
派生类的虚函数与基类中对应的虚函数必须满足下列条件,否则派生类中的虚函数将丢失其虚特性,在调用时进行静态联编:●派生类中的虚函数与基类中的虚函数具有相同的名称:●派生类中的虚函数与基类中的虚函数具有相同的参数个数和相同的对应参数类型:●派生类中的虚函数与基类中的虚函数的返回值或者相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中的虚函数所返回的指针或引用的基类型的子类型。
3 在下面四个选项中,________是用来声明虚函数的。
AA)virtual B)public C)using D)false注释:说明虚函数的一般格式如下:virtua1<函数返回类型><函数名>(<参数表>)4 对虚函数的调用________。
多态常见的经典面试题
![多态常见的经典面试题](https://img.taocdn.com/s3/m/04f77ed1aa00b52acfc7ca4a.png)
1、请谈谈你对多态的理解?谈多态的现象
2、谈谈C++编译器是如何实现多态?谈C++编译器实现原理
3、重写PK 重载理解谈多态的现象
4、是否可以将类的每个成员函数都声明为虚函数,为什么。
谈多态的C++编译器实现原理
说明1:
通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。
而普通成员函数是在编译时就确定了调用的函数。
在效率上,虚函数的效率要低很多。
说明2:
出于效率考虑,没有必要将所有成员函数都声明为虚函数
5、构造函数中调用虚函数能实现多态吗?为什么?谈多态的C++编译器实现原理
能调用但是不是多态,具体见视频
6、虚函数表指针(vptr)被编译器初始化的过程,你是如何理解的?同上
7、构造函数能定义成虚函数吗?谈多态的现象
8、为什么要定义虚析构函数?虚析构函数的应用场景?谈多态的应用
需要通过多态(积累指针去)释放子类对象资源
9、铁律1:指针也是一种数据类型,在C++中的引申
积累和子类对象指针++混搭风
被实际开发经验抛弃的多继承
工程开发中真正意义上的多继承几乎是不被使用的
多重继承所带来的代码复杂性远高于其所带来的便利性
多重继承对于代码维护性上的影响是灾难性的
在设计方法上,任何多继承都可以用单继承代替
绝大多数面向对象语言都不支持多继承
绝大多数面向对象语言都支持接口的概念
C++中没有接口的概念
C++中可以使用纯虚函数实现接口
接口类中只有函数原型定义,没有任何数据的定义。
构造函数虚构函数是否可为虚函数
![构造函数虚构函数是否可为虚函数](https://img.taocdn.com/s3/m/b491e57a302b3169a45177232f60ddccda38e63c.png)
构造函数虚构函数是否可为虚函数构造函数:为对象分配存储空间,使一个对象初始化;析构函数:在该对象生命期完结时做相应的扫尾工作并释放由构造函数分配的内存;构造函数不能是虚函数的原因:自己的话:【只有基类指针指向子类对象时,虚函数才用意义。
当一个基类指针指向子类对象时,子类对象已经构造好了,已经没有动态绑定的必要了,所以虚函数不能是虚函数。
】从概念上来说,如前所述,虚函数机制只有在应用于地址时才有效,因为地址在编译阶段提供的类型信息不完全。
构造函数的功能是为一个对象在内存中分配空间,也就是说,此时该对象的类型已经确定了,编译系统确切的知道应该调用哪一个类的构造函数,不需要也不可能应用动态绑定。
从实现上来说,每个对象的VPTR是需要构造函数来初始化的(当然是由编译系统自动加进去的代码来实现),在构造函数没有调用之前,VPTR没有形成,根本就不可能实现动态绑定。
当构造函数内部有虚函数时,会出现什么情况呢?结果是,只有在该类中的虚函数版本被调用,也就是说,在构造函数中,虚函数机制不起作用了,调用虚函数如同调用一般的成员函数一样。
析构函数可以是虚函数:析构函数可以是虚函数,而且应该被声明为虚函数。
与一般成员函数相似,析构函数被调用时,对象的构造已经完成,VPTR和VTABLE也已被正确初始化,因此虚析构函数在实现上是可能的。
从设计角度来看,析构函数的任务是释放内存,因此它必须确切知道被释放的对象的类型,否则可能破坏有用的数据,产生不可预知的后果。
例如,我们用基类指针指向了派生类对象,那么释放内存时,必须是释放派生类对象的存储空间。
所以,析构函数经常被声明为虚函数。
由于效率上的原因,并不把析构函数缺省为虚函数。
但作为一条实践经验,可以给有虚函数的每个基类声明虚析构函数。
当析构函数内部有虚函数时,又如何工作呢?与构造函数相同,只有“局部”的版本被调用。
但是,行为相同,原因是不一样的。
构造函数只能调用“局部”版本,是因为调用时还没有派生类版本的信息。
构造函数 虚函数
![构造函数 虚函数](https://img.taocdn.com/s3/m/96a6162ebfd5b9f3f90f76c66137ee06eff94eba.png)
构造函数虚函数1.构造函数构造函数是一种特殊的函数,用于创建对象时执行必要的初始化任务。
每个类只能有一个构造函数,并且与类同名。
构造函数不能有返回类型,即使是void也不行;并且不能被声明为const、volatile或const volatile。
以下是一些常见的构造函数类型:默认构造函数:无参构造函数,当创建对象时没有提供参数时即被调用。
拷贝构造函数:接受const&类型的引用参数,用于创建新对象并将旧对象的数据复制到新对象中。
带参构造函数:接受一个或多个参数,用于在创建对象时进行初始化。
析构函数是与构造函数相对应的,用于对象销毁前执行必要的清理任务。
析构函数与构造函数一样只有一个,并且没有返回类型。
2.虚函数虚函数是在运行时动态绑定,即通过基类指针或引用调用时会根据实际指向的对象的类型来调用相应的派生类函数的一种函数。
通过将基类成员函数声明为虚函数,可以实现多态性,使得基类指针或引用可以指向任意派生类对象。
以下是虚函数的一些特点:虚函数的函数名前面要加上virtual关键字,作为函数声明的一部分。
虚函数可以有实现,也可以没有,如果没有实现,则称为纯虚函数,需要在基类中声明为纯虚函数,即在函数后面加上=0,派生类必须重写该函数。
虚函数必须是非静态成员函数,不能是全局函数,也不能是静态成员函数。
虚函数的使用必须通过基类的指针或引用进行调用,不能直接调用。
派生类中重写基类的虚函数时,函数签名(函数名、参数列表、返回类型)必须与基类的虚函数相同,可见性可以更宽。
3.中文中文是不同于拉丁字母的表音文字系统,使用口音来对应一系列符号。
中文是全球使用最广泛的文字之一,被认为是人类文明中的重要发明之一。
中文分为繁体字和简体字两种形式,繁体字主要在香港、台湾等地使用,而简体字则是中国大陆通用。
现在,许多程序界面和文档被翻译成了中文,在程序设计和开发方面,学习中文也是很有用的一项技能。
总结构造函数和虚函数是C++中很重要的两个概念。
构造函数可以是虚函数吗?构造函数和析构函数可以调用虚函数吗?虚表和虚表指针的概念
![构造函数可以是虚函数吗?构造函数和析构函数可以调用虚函数吗?虚表和虚表指针的概念](https://img.taocdn.com/s3/m/b6e35677f4335a8102d276a20029bd64783e628c.png)
构造函数可以是虚函数吗?构造函数和析构函数可以调⽤虚函数
吗?虚表和虚表指针的概念
构造函数不可以是虚函数。
因为类的虚函数表指针是在构造函数中初始化的,在虚表指针没有被正确初始化之前,我们不能调⽤虚函数。
构造函数和析构函数也不能调⽤虚函数,前者是因为虚表指针还没有被初始化,后者是因为虚表指针可能已经被析构了。
0i
存在虚函数的类都有⼀个⼀维的虚函数表,简称虚表。
类的每个对象都有⼀个指向虚表开始的虚表指针。
虚表是和类对应的,虚表指针是和对象对应的。
抽象类是指⾄少包含⼀个纯虚函数的类。
编译器在编译的时候发现类⾥有虚函数就会为该类创建⼀个虚表。
编译器另外还为每个类的对象提供⼀个虚表指针,这个指针指向对象所属的类的虚表。
在虚表指针没有被正确初始化之前,我们不能调⽤虚函数。
虚表的创建和虚表指针的初始化都是在构造函数中完成的。
派⽣类的虚函数表存放重写的虚函数,当基类的指针指向派⽣类的对象时,调⽤虚函数时都会根据虚表指针来选择虚函数。
⽽基类的虚函数在派⽣类中已经被重写了,因此只能调⽤派⽣类的虚函数版本了。
**这也揭⽰了虚析构函数的作⽤,当基类指针指向派⽣类对象时,调⽤虚析构函数会先调⽤派⽣类的析构函数。
如果析构函数不是虚函数,那么基类指针调⽤析构函数时只会调⽤基类的析构函数。
重载函数属于编译时多态,虚函数属于运⾏时多态。
虚函数的概念与作用
![虚函数的概念与作用](https://img.taocdn.com/s3/m/2431273f591b6bd97f192279168884868662b843.png)
虚函数的概念与作用一、概念虚函数是C++中的一个重要概念,它是一种在基类中声明的函数,该函数在派生类中被重新定义。
虚函数可以通过基类指针或引用来调用,在运行时确定调用的是哪个版本的函数。
虚函数通过动态绑定实现了多态性,是C++中实现面向对象编程的重要手段之一。
二、作用1. 实现多态性虚函数通过动态绑定实现了多态性,使得同一个基类指针或引用可以调用不同派生类的同名函数,从而实现了多态性。
这样就可以在编写程序时避免使用大量的if-else语句或switch语句来判断对象类型,提高了程序的可读性和可维护性。
2. 简化代码使用虚函数可以简化代码,减少代码量。
如果没有使用虚函数,则需要为每个派生类分别编写相应的处理代码,在程序规模较大时会导致代码冗长、难以维护和扩展。
3. 便于扩展使用虚函数可以方便地扩展程序功能。
当需要添加新的派生类时,只需要重新定义相应的虚函数即可,在原有代码基础上进行扩展,而不需要修改已有代码。
4. 支持动态类型识别使用虚函数可以支持动态类型识别。
在程序运行时,可以通过基类指针或引用来判断对象的实际类型,从而进行相应的处理。
这种机制在实现一些高级特性时非常有用,如RTTI(Run-Time Type Identification)。
5. 支持多重继承使用虚函数可以支持多重继承。
在多重继承中,一个派生类可以同时继承多个基类,每个基类都可能定义相同的虚函数。
如果没有使用虚函数,则会导致二义性错误(Ambiguity),而使用虚函数则可以避免这种问题的发生。
三、注意事项1. 虚函数必须是成员函数虚函数必须是成员函数,不能是全局函数或静态成员函数。
2. 构造函数和析构函数不能是虚函数构造函数和析构函数不能是虚函数,因为它们的调用方式不同于普通成员函数。
3. 虚析构函数如果一个类中定义了虚析构函数,则当该类被删除时,会自动调用其派生类的析构函数。
这样可以确保所有资源都被正确释放。
4. 纯虚函数与抽象类如果一个基类中定义了纯虚函数,则该基类就变成了抽象类。
virtual函数(在构造函数和析构函数的运用)
![virtual函数(在构造函数和析构函数的运用)](https://img.taocdn.com/s3/m/5bde4008eef9aef8941ea76e58fafab069dc44a6.png)
virtual函数(在构造函数和析构函数的运⽤)⼀、为多态基类声明virtual析构函数base classes应该声明⼀个virtual析构函数,如果带有virtual函数,就应该拥有⼀个virtual析构函数。
如果带有多态性质的base classes不具有virtual析构函数,当derived class 对象经由base class指针被删除,则derived成分不会被删除。
如果class不企图成为⼀个base class时或者不是为了具备多态性,则不应该声明virtual的析构函数。
virtual函数的出现必然需要携带某些信息并由vptr指针指出,从⽽对象的体积。
⼆、构造函数不应该声明成virtual1 构造⼀个对象的时候,必须知道对象的实际类型,⽽虚函数⾏为是在运⾏期间确定实际类型的。
⽽在构造⼀个对象时,由于对象还未构造成功。
编译器⽆法知道对象的实际类型,是该类本⾝,还是该类的⼀个派⽣类,或是更深层次的派⽣类。
2 虚函数的执⾏依赖于虚函数表。
⽽虚函数表在构造函数中进⾏初始化⼯作,即初始化vptr,让他指向正确的虚函数表。
⽽在构造对象期间,虚函数表还没有被初始化,将⽆法进⾏。
三、不在构造和析构过程调⽤virtual函数在derived类对象的构造过程是先构造base class成分再构造derived class成分,中如果含有virtual函数,则有可能出现如下情况:base class与derived class定义⼀个virtual函数,在derived class对象构造过程会⽣成base成分中对应的virtual函数,从⽽造成错误。
换句话说base class构造期间virtual函数不会下降到derived class阶层,在析构的过程也⼀样,⼀旦derived class析构函数开始执⾏,对象内的derived成员成分就呈现未定义的值,再进⼊base class析构函数时此时对应的函数不复存在了。
c++ 纯虚类 构造函数
![c++ 纯虚类 构造函数](https://img.taocdn.com/s3/m/fbe0225730b765ce0508763231126edb6e1a7671.png)
c++ 纯虚类构造函数C++是一种面向对象的编程语言,它支持纯虚类的概念,通过使用纯虚类,可以定义一组共性的接口,从而实现多态性,即不同的派生类可以使用相同的接口来实现不同的行为。
在使用纯虚类的过程中,我们也需要注意到纯虚类的构造函数问题。
在C++中,纯虚类是指含有至少一个纯虚函数的抽象类,它不能被直接实例化,只能用作其他类的基类。
纯虚函数是没有实现的函数,它的实现由派生类完成。
我们可以通过在函数声明中加上“=0”来将一个虚函数声明为纯虚函数。
例如:```class Shape{public:virtual void draw() = 0;};```上述代码中,Shape类是一个纯虚类,它只有一个纯虚函数draw(),因此Shape类不能被实例化。
但是,当我们使用纯虚类时,需要注意它的构造函数问题。
由于纯虚类不能被实例化,因此其构造函数也不能被调用。
但是,派生类在实例化时,需要调用基类的构造函数,以完成一些基本的初始化工作,那么该如何解决这个问题呢?解决方法是,在纯虚类中提供一个保护的构造函数,该构造函数只能被派生类调用,并在派生类的构造函数中调用它。
例如: ```class Shape{protected:Shape(){};public:virtual void draw() = 0;};class Circle : public Shape{public:Circle(){// do something};virtual void draw(){// do something};};int main(){Circle circle;return 0;}```在上述代码中,Shape类提供了一个保护的构造函数,只能被派生类调用,在Circle类的构造函数中,通过调用基类的构造函数来完成基本的初始化工作。
这样,我们就解决了纯虚类构造函数的问题。
总之,纯虚类是C++中面向对象编程的重要概念之一,它通过定义一组共性的接口,实现了多态性。
构造函数虚函数
![构造函数虚函数](https://img.taocdn.com/s3/m/f75aab26bb1aa8114431b90d6c85ec3a87c28beb.png)
构造函数虚函数构造函数虚函数是指在类定义中使用virtual关键字声明的构造函数。
构造函数虚函数可以起到多态的作用,提高代码的可扩展性和灵活性,是一种常见的设计模式。
首先,在定义构造函数虚函数前,需要在类定义头部使用virtual关键字声明。
以下是示例:class A {public: virtualA();};在使用virtual关键字声明的构造函数中,可以定义一些虚函数,它们可以由子类重载,以满足不同的需求,而无需改变父类的定义。
例如:class A {public: virtual A(); // 基类构造函数 virtual void doSomething(); // 基类函数};class B : public A {public: B(); // 子类构造函数 void doSomething(); // 子类函数,重载基类函数};构造函数虚函数的一个重要作用是可以在编译时实现多态,即根据实际的对象类型,调用不同的构造函数。
在下面的例子中,在A类的基础上,定义了B 类:A* a = new B(); // 创建一个B类的对象此时,尽管变量a 是A类的指针,但实际上创建的是B类的对象,在编译时,编译器会自动调用B类的构造函数,从而实现多态。
另外,构造函数虚函数也可以用于实现“工厂模式”,用于动态创建不同类型的对象。
下面是一个例子:class A {public: virtual A* createObject(); // 工厂函数,可以创建不同类型的对象};class B : public A {public: B(); // 子类构造函数 A* createObject(); // 子类函数,重载基类函数};此时,当调用A::createObject()时,编译器会根据实际的对象类型来调用不同的createObject,从而实现动态创建不同类型的对象。
总之,构造函数虚函数可以起到多态的作用,提高代码的可扩展性和灵活性,是一种常见的设计模式。
子类构造函数中调用虚函数问题验证
![子类构造函数中调用虚函数问题验证](https://img.taocdn.com/s3/m/beda96e2112de2bd960590c69ec3d5bbfd0adaaf.png)
⼦类构造函数中调⽤虚函数问题验证⼀、问题探讨 ⼤家是否有在⼦类或基类构造函数中调⽤虚函数的情况呢?语法是否⽀持?执⾏情况如何呢?为什么呢?⼆、问题验证 这个问题的最好答案是亲⾃写个demo调试下看看结果如何,下⾯是demo代码(vs2019):1class Base {2public:3 Base() {4 cout << "Base::ctor\n";5 Func();6 }7virtual void Func() {8 cout << "Base::Func\n";9 }10 };1112class D : public Base {13public:14 D() {15 cout << "D::ctor\n";16 Func();17 }1819void Func() override {20 cout << "D::Func\n";21 }22 }; 客户端调⽤代码1int main()2 {3 auto d = std::make_shared<D>();45return0;6 } 输出结构如下: ⼦类构造函数调⽤虚函数调⽤的是⼦类重写的,基类构造函数调⽤的是基类的虚函数。
可以回答问题中的前两个疑问,语法⽀持,执⾏情况如上;但是并没有调⽤⼦类重新的虚函数呢?继续分析。
三、原因分析 回答这个问题,我们可以直接使⽤VS2019的调试器即可。
我们需要在基类和⼦类的构造函数中分别打上断点,如下: 我们知道构造⼦类前需要先构造基类,在进⼊基类构造函数实现体前,虚函数表是空的: 进⼊构造体后,虚表指向基类的虚函数地址: 进⼊⼦类构造体前,虚表还是基类的虚函数地址: 进⼊⼦类构造体后,虚表地址替换为⼦类的: 通过上⾯详细的调试步骤和对象内存虚表信息即可知道为什么了吧!。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
<< C180.foo this: 0012F7A4 vtadr: CCCCCCCC
3) 如果对象有父类就会先调用父类的构造函数(并依次递归),如果有多个父类(多重继承)会依次对父类的构造函数进行调用,并会适当的调整this指针的位置。在调用完所有的父类的构造函数后,再执行自己的代码。
照上面的分析构造C190时也会调用C180的构造函数,这时在C180构造函数中的第一个foo调用为静态绑定,会调用到C180::foo()函数。第二个foo调用是通过指针调用的,这时多态行为会发生,应该调用的是C190::foo()函数。
17 0042700E mov dword ptr [eax],45C400h
18 00427014 mov eax,dword ptr [ebp-8]
19 00427017 pop edi
20 00427018 pop esi
21 00427019 pop ebx
22 0042701A add esp,0CCh
为此我查了一下C++标准规范。在12.7.3条中有明确的规定。这是一种特例,在这种情况下,即在构造子类时调用父类的构造函数,而父类的构造函数中又调用了虚成员函数,这个虚成员函数即使被子类重写,也不允许发生多态的行为。即,这时必须要调用父类的虚函数,而不子类重写后的虚函数。
我想这样做的原因是因为在调用父类的构造函数时,对象中属于子类部分的成员变量是肯定还没有初始化的,因为子类构造函数中的代码还没有被执行。如果这时允许多态的行为,即通过父类的构造函数调用到了子类的虚函数,而这个虚函数要访问属于子类的数据成员时就有可能出错。
在这种情况下产生的汇编代码我就不列了,有兴趣的朋友可以自己去看一看。另外对于析构函数的调用,也请有兴趣的朋友自行分析一下。
另外这个属性在ATL的代码中大量的使用。在ATL中接口一般为纯虚基类,如果不用这个优化属性,由于在子类即实现类的构造函数中要调用父类的构造函数,而编译器产生的父类构造函数又要设置虚指针的值。所以编译器必须要把父类的虚表构建出来。而实际上这个虚表是没有任何意义的,因为ATL的纯虚接口类的虚函数都是无实现的。这样不仅仅是多了几行无用的设值指令,同时也浪费了空间。有兴趣的朋友可以自行验证一下。
执行如下代码:
C190 obj;
obj.foo();
结果为:
<< C180.foo this: 0012F7A4 vtadr: 0045C404
<< C180.foo this: 0012F7A4 vtadr: 0045C404
<< C190.foo this: 0012F7A4 vtadr: 0045C400
23 00427020 cmp ebp,esp
24 00427022 call 0041DDF2
25 00427027 mov esp,ebp
26 00427029 pop ebp
27 0042702A ret
开始部分的指令在前面几篇中陆续解释过,这里不再详述。我们看看第15是对父类的构造函数C180::C180()的调用,根据前文的说明,我们知道此时ecx中放的是this指针,也就是C190对象的地址。这时如果跳到this指针批向的地址看看会发现值为0xcccccccc即没有初始化,虚表指针也没有被初始化。那么我们跟着跳到C180的构造函数看看。
25 00427088 cmp ebp,esp
26 0042708A call 0041DDF2
27 0042708F mov esp,ebp
28 00427091 pop ebp
29 00427092 ret
看看第15行,在this指针的位置也就是对象的起始处,填入了一个4字节的值0x0045C404,其实这就是我们前面的打印过的C180的虚表地址。第16、17行和18、19行分别调用了两次foo()函数,用的都是静态绑定。这个就有点奇怪,因为对后一个调用我们使用了this指针,照理应该是动态绑定才对。可这里却是静态绑定,为什么编译器要做这个优化?我们继承往后看。
C++中构造函数中调用虚函数的问题
在构造函数中调用虚成员函数,虽然这是个不很常用的技术,但研究一下可以加深对虚函数机制及对象构造过程的理解。这个问题也和一般直观上的认识有所差异。先看看下面的两个类定义。
struct C180
{
C180() {
foo();
this->foo();
还有值得一提的是,VC7.1中有一个扩展属性可以用来抑制编译器产生对虚指针进行调整的代码。我们可以在C180类的声明中加入这个属性。
struct __declspec(novtable) C180
{
C180() {
foo();
this->foo();
}
virtual foo() {
<< C180.foo this: 0012F7A4 vtadr: CCCCCCCC
<< C190.foo this: 0012F7A4 vtadr: 0045C400
由于编译器抑制了对虚指针的调整所以在调C180的构造函数时虚指针的值没有初始化,这时我们才看到多亏编译器把第二个通过this指针对foo的调用优化成了静态绑定,否则由于虚指针(指的就是this)没有初始化一定会出现一个指针异常的错误,这就回答我们上面的那个问题。
我们可以来预测一下如果构造一个C190的对象会发生什么情况。
我们知道,在构造一个对象时,过程是这样的:
1) 首先会按对象的大小得到一块内存(在heap上或在stack上),
2) 把指向这块内存的指针做为this指针来调用类的构造函数,对这块内存进行初始化。
cout << "<< C190.foo this: " << this << " vtadr: " << *(void**)this << endl;
}
};
父类中有一个虚函数,并且父类在它的构造函数中调用了这个虚函数,调用时它采用了两种方法一种是直接调用,一种是通过this指针调用。同时子类又重写了这个虚函数。
14 00427063 mov eax,dword ptr [ebp-8]
15 00427066 mov dword ptr [eax],45C404h
16 0042706C mov ecx,dword ptr [ebp-8]
17 0042706F call 0041DA8C
18 00427074 mov ecx,dword ptr [ebp-8]
01 00427040 push ebp
02 00427041 mov ebp,esp
03 00427043 sub esp,0CCh
04 00427049 push ebx
05 0042704A push esi
06 0042704B push edi
07 0042704C push ecx
08 0042704D lea edi,[ebp+FFFFFF34h]
09 00427053 mov ecx,33h
10 00427058 mov eax,0CCCCCCCCh
11 0042705D rep stos dword ptr [edi]
12 0042705F pop ecx
13 00427060 mov dword ptr [ebp-8],ecx
12 00426FFF pop ecx
13 00427000 mov dword ptr [ebp-8],ecx
14 00427003 mov ecx,dword ptr [ebp-8]
15 00427006 call 0041D451
16 0042700B mov eax,dword ptr [ebp-8]
06 00426FEB push edi
07 00426FEC push ecx
08 00426FED lea edi,[ebp+FFFFFF34h]
09 00426FF3 mov ecx,33h
10 00426FF8 mov eax,0CCCCCCCCh
11 00426FFD rep stos dword ptr [edi]
19 00427077 call 0041DA8C
20 0042707C mov eax,dword ptr [ebp-8]
21 0042707F pop edi
22 00427080 pop esi
23 00427081 pop ebx
24 00427082 add esp,0CCh
和我们的分析大相径庭。第一行是在C180中运行foo()函数得到的,这里的foo()当然是调用C180中的foo()函数。第二行是调用C180中的this->foo()得到的,此时this指向的应该是C180的虚表地址,按照调用规则,应该是动态绑定,即,此时若派生类对该虚函数实现过,则应该调用派生类的虚函数,这里是一个例外,下面会详细讲到。 至此,C190的父类的构造函数运行完毕,转而运行C190的构造函数,但是这里C190的构造函数什么都没有。第三行是在main函数中调用obj.foo()得到的,这里直接进入C190运行就可以了。 这里必须注意一点,就是前两行和第三行的虚表是不同的,这是因为前两行的虚表是C180的虚表,而第三行的虚表是C190的虚表。 其实这正是奥秘所在。