还原virtual函数的本质-----C++
当你每次看到C++类中声明一个virtual函数,特别是看到了一个virtual的虚构函数。你知道它的意思吗?你肯定会毫不犹豫的回答:不就是多态么。。。在运行时确定具体的行为么。。。完全正确,但这里我要讲的不只是这些东西。
有些类需要虚函数,有些不需要虚函数。这是为什么,一般你看到的类如果有一个虚析构函数,那么这个类中应该会有至少一个是虚函数的。。这是为什么呢??如果我们类中没有用其他虚函数的话,你创建了这个也是多余的,而且会增加类对象的大小。。说这些纯理论的东西,也许大家不知所云。。下面我就给例子来验证。。
1:
class A
{
public:
A(){};
// virtual ~A(){};
~A();
};
void main()
{ A a;
cout<<sizeof(a)<<endl;
}
结果为1。这个1应该是编译器自己为它加上的。。哪怕你不在类中不写任何东西,它也是1;例如;
class A
{ };
void main()
{ A a;
cout<<sizeof(a)<<endl;
}
如果你把析构函数声明为虚函数。。如:
2:
class A
{
public:
A(){};
virtual ~A(){};
//~A();
};
void main()
{
A a;
cout<<sizeof(a)<<endl;
}
结果是4。先不说这是为什么。。
然后还是说一下关于虚函数基础的东西(多态)吧,也给个例子:
#include<iostream>
#include<string>
using namespace std; class Base
{
public:
Base();
virtual ~Base(); virtual void test();
private :
int count;
};
Base::Base(){
cout<<"Base部分创建了"<<endl;
}
Base::~Base(){
cout<<"Base部分被销毁了"<<endl;
}
void Base::test()
{
cout<<"Base Test"<<endl; } class Derive1:public Base
{
public:
Derive1();
virtual ~Derive1(); void test(); };
Derive1::Derive1(){
cout<<"子类部分创建了"<<endl;
}
Derive1::~Derive1(){
cout<<"子类部分被销毁了"<<endl;
}
void Derive1::test()
{
cout<<"Derive1 Test"<<endl;
} void main()
{
Base* d1=new Derive1();
d1->test();
delete d1; }
由此看见,当通过声明一个父类指针并且让它指向一个子类的对象,在子对象创建的时候,会先去调用父类的构造函数,然后再是自己的构造函数,当通过父类指针去调用一个虚函数test()时,它实际上回去调用子类的test()函数,这是为什么呢,它肯定有什么信息让它这样做吗。。这个信息肯定是子类对象给它的。。这个信息就是虚函数指针(vptr),它指向一个虚函数表(vtbl),这个虚函数表其实就是包含了这个类的所有虚函数的函数名(函数指针),每个类就只包含了那一个虚函数指针和它的一些成员变量。这下可以解释上面为什么是1,为什么是4了。。
在win32的机器上,每个指针是4字节。刚才也提到每个类的大小取决于两部分,一个是成员变量,一个是虚函数指针而且有且只有一个,在例子一中,因为没有成员变量,而有一个虚函数---析构函数,此时肯定会有一个虚函数指针,所以是4。。 其实刚才也就同时说清楚了多态的本质,就是子对象的虚函数指针给出了这个信息,父类指针才知道去执行哪个函数。。
最后一个问题:为什么析构函数要声明为虚函数呢?(当至少有一个为虚函数的时候)
从刚才的那个结果也可以看出,当我们delete那个指针的时候,会发生析构,而且这个过程是从子类到父类的顺序进行。假如此时析构函数不为虚函数,父类指针也就不知道去执行子类的析构函数。。也就不会去释放子对象的那部分内存,造成内存泄漏。。例如:(这里我们只是对上一段代码进行修改,去掉了父类中的virtual):
#include<iostream>
#include<string>
using namespace std; class Base
{
public:
Base();
~Base(); virtual void test();
private :
int count;
};
Base::Base(){
cout<<"Base部分创建了"<<endl;
}
Base::~Base(){
cout<<"Base部分被销毁了"<<endl;
}
void Base::test()
{
cout<<"Base Test"<<endl; } class Derive1:public Base
{
public:
Derive1();
~Derive1(); void test(); };
Derive1::Derive1(){
cout<<"子类部分创建了"<<endl;
}
Derive1::~Derive1(){
cout<<"子类部分被销毁了"<<endl;
}
void Derive1::test()
{
cout<<"Derive1 Test"<<endl;
} void main()
{
Base* d1=new Derive1();
d1->test();
delete d1; }
所以当至少有一个虚函数的话,我们也要把它的析构函数声明为virtual。(插一句:有些人会说你子类中的那个函数哪里是虚函数哦,我没看到virtual 啊。。其实C++允许我们这样做,重写父类的虚函数,不是必须要声明出来的。)
总结:一个类有了虚函数,是为了成为一个基类,如果不是这样的话,那么父类中的任何函数都没有必要是虚函数,甚至会增加类的大小。多态告诉了我们这点。。一旦成为了基类,那么就要把析构函数声明为一个虚函数。。
好了,虚函数的内容就Over了。。。。。
还原virtual函数的本质-----C++的更多相关文章
- 读书笔记_Effective_C++_条款三十五:考虑virtual函数以外的其他选择
举书上的例子,考虑一个virtual函数的应用实例: class GameCharacter { private: int BaseHealth; public: virtual int GetHea ...
- 09——绝不在构造和析构函数中调用virtual函数
在base class构造期间,virtual函数不是virtual函数. 构造函数.析构函数中不要调用virtual函数.
- 考虑virtual函数以外的选择
在C++中,有四种选择可以替代virtual函数的功能: 1.non-virtual interface(NVI)手法,这是一种template method模式.它以public non-virtu ...
- Effective C++ -----条款35:考虑virtual函数以外的其他选择
virtual函数的替代方案包括NVI手法及Strategy设计模式的多种手法.NVI手法自身是一个特殊形式的Template Method设计模式. 将机能从成员函数移到class外部函数,带来的一 ...
- Effective C++ -----条款09:绝不在构造和析构过程中调用virtual函数
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层).
- 为什么内联函数,构造函数,静态成员函数不能为virtual函数
http://blog.csdn.net/freeboy1015/article/details/7635012 为什么内联函数,构造函数,静态成员函数不能为virtual函数? 1> 内联函数 ...
- 条款9:不要在构造和析构过程中调用virtual函数
如下是一个股票交易的例子: class Transaction // 交易的基类 { public: Transaction(); ; // 用于记录交易日志 }; Transaction::Tran ...
- effective c++:virtual函数的替代方案
绝不重新定义继承来的缺省值 首先明确下,虚函数是动态绑定,缺省参数值是静态绑定 // a class for geometric shapes class Shape { public: enum S ...
- effective c++:virtual函数在构造函数和析构函数中的注意事项
如不使用自动生成函数要明确拒绝 对于一个类,如果你没有声明,c++会自动生成一个构造函数,一个析构函数,一个copy构造函数和一个copy assignment操作符. class Empty { p ...
随机推荐
- mysql update不能直接使用select的结果
在sql server中,我们可是使用以下update语句对表进行更新:update a set a.xx= (select yy from b) ;但是在mysql中,不能直接使用set selec ...
- Curl 采集乱码 gzip 原因及解决方案 utf-8
用curl获取一个经过gzip压缩后的网页时返回乱码 原因大体就是服务器返回的Content-Encoding的值和网页的编码不同,造成curl解码出问题,直接将gzip或deflate编码的文件下载 ...
- Android Camera拍照 压缩
http://www.linuxidc.com/Linux/2014-12/110924.htm package com.klp.demo_025; import java.io.ByteArrayI ...
- python运维开发之第二天
一.模块初识: 1.模块定义 python是由一系列的模块组成的,每个模块就是一个py为后缀的文件,同时模块也是一个命名空间,从而避免了变量名称冲突的问题.模块我们就可以理解为lib库,如果需要使用某 ...
- General Purpose Hash Function Algorithms
General Purpose Hash Function Algorithms post@: http://www.partow.net/programming/hashfunctions/inde ...
- iOS版 hello,world版本2
// // main.m // Hello // // Created by lishujun on 14-8-28. // Copyright (c) 2014年 lishujun. All rig ...
- bzoj 1079: [SCOI2008]着色方案 DP
1079: [SCOI2008]着色方案 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 803 Solved: 512[Submit][Status ...
- Html和JS基础
1.body:bgcolor,background(背景图片),bgproperities=fixed(图片水印),text(正文颜色). 2.hr:水平分割线,正文标题<h?>自动换行了 ...
- 对话机器学习大神Yoshua Bengio(下)
对话机器学习大神Yoshua Bengio(下) Yoshua Bengio教授(个人主页)是机器学习大神之一,尤其是在深度学习这个领域.他连同Geoff Hinton老先生以及 Yann LeCun ...
- matlab提速技巧(自matlab帮助文件)
matlab提速技巧(自matlab帮助文件) 1.首先要学会用profiler.1.1. 打开profiler.To open the Profiler, select View -> Pro ...