C++ 构造函数、析构函数与虚函数的关系
编译环境:windows 10 + VS2105
1、构造函数不能为虚函数
虚函数的作用是为了实现C++多态机制。基类定义虚函数,子类可以重写该虚函数。当子类重写父类虚函数后,父类指针指向子类地址时,父类指针根据赋给它不同子类的指针,动态调用该子类的该函数,而不是父类的对应函数(当子类没重写该函数时,则调用父类对应的函数)。且这样的函数调用发生在运行阶段,而不是发生的编译阶段,称为动态联编(注意:函数重载也可以认为是多态,只不过是静态的,在编译阶段确定了函数调用方式。非虚函数静态联编效率比虚函数高,但是不具备动态联编能力)。
class A
{
public:
virtual void fun_1() { std::cout << "A::fun_1" << std::endl; }
virtual void fun_2() { std::cout << "A::fun_2" << std::endl; }
};
class B :public A
{
public:
void fun_1() { std::cout << "B::fun_1" << std::endl; }
//void fun_2() { std::cout << "B::fun_2" << std::endl; }
}; int main()
{
B b;
A* obj = &b;
obj->fun_1(); //输出 "B::fun_1"
obj->fun_2(); //子类没重写fun_2,所以调用父类的 fun_2 输出"A::fun_2"
return 0;
}
因此,虚函数是只知道部分信息情况下完成函数调用的机制,允许我们只知道接口而不知道对象的确切类型。但是要创建一个对象,则需要知道对象的一个完整信息。所以不支持构造函数是虚函数。另外,一般情况下,编译器为虚函数维护一个虚函数列表。类在构造时候需要分配内存来构造对象,构造对象没完成时,虚函数表不存在,如果构造函数是虚函数,这个虚函数表并没有创建出来,因此会陷入死锁。编译器会认为此写法不合法。
2、析构函数可以为虚函数
1)析构顺序
派生类的成员类->派生类->基类
2)基类析构函数为非虚函数时,造成内存泄漏。
下列代码造成内存泄漏,原因是直接给编译器一个A指针,编译器直接调用A的析构函数。
class A
{
public:
~A() {std::cout << "~A()" << std::endl;}
};
class B :public A
{
public:
C* c =nullptr;
B() { c = new C(); }
~B()
{
delete c;
std::cout << "~B()" << std::endl;
}
}; int main()
{
A* obj = new B();
delete obj; //输出"~A()",没调用B的析构函数,有可能造成内存泄漏 (B中 c资源没释放)
return 0;
}
3)基类析构函数定义成虚函数可避免内存泄漏。
class C{
public:
~C() { std::cout << "~C()" << std::endl; }
};
class A
{
public:
virtual~A() {std::cout << "~A()" << std::endl;}
};
class B :public A
{
public:
C* c =nullptr;
B() { c = new C(); }
~B()
{
delete c;
std::cout << "~B()" << std::endl;
}
}; int main()
{ A* obj = new B();
delete obj;
return 0;
}
/*
输出:
~C()
~B()
~A()
内存正常释放
*/
4)纯虚析构函数
定位为纯虚函数的析构函数称为纯虚析构函数。一般我们把函数设置为纯虚函数时不想这个类实例化,抽象出来的顶层父类,并且这个纯虚函数不能实现。与普通纯虚析构函数区别是不能在类中 = 0之后实现,而需要类外实现。如果不是实现,则编译器会自动加上。同样,编译器仍会对其产生调用。
class C {
public:
~C() { std::cout << "~C()" << std::endl; }
};
class A
{
public:
virtual~A() = 0;
};
A::~A() { std::cout << "~A()" << std::endl; }
class B :public A
{
public:
C* c = nullptr;
B() { c = new C(); }
~B()
{
delete c;
std::cout << "~B()" << std::endl;
}
}; int main()
{
A* obj = new B();
delete obj;
return 0;
} /*
输出:
~C()
~B()
~A()
内存正常释放
*/
与上一段代码效果一样。
5)关于virtual的隐式传播
class A
{
public:
virtual~A() = 0;
};
A::~A() { std::cout << "~A()" << std::endl; }
class B :public A
{
public:
~B()
{
std::cout << "~B()" << std::endl;
}
};
class C:public B {
public:
~C() { std::cout << "~C()" << std::endl; }
};
class D:public C {
public:
~D() { std::cout << "~D()" << std::endl; }
}; int main()
{
A* obj = new D();
delete obj;
return 0;
} /*
输出:
~D()
~C()
~B()
~A()
*/
当基类是虚函数,无论子类的相同函数是否加virtual关键字均为虚函数。但是为了方便其他开发人员查阅代码,建议把从继承过来的虚函数都加上virtual关键字。
使用虚函数代表会增加一个指针内存开销。
C++ 构造函数、析构函数与虚函数的关系的更多相关文章
- 转 C++构造函数、析构函数、虚函数之间的关系
C++构造函数.析构函数.虚函数之间的关系 1. 如果我们定义了一个构造函数,编译器就不会再为我们生成默认构造函数了.2. 编译器生成的析构函数是非虚的,除非是一个子类,其父类有个虚析构,此时的函数虚 ...
- C++中为什么构造函数不能是虚函数,析构函数是虚函数
一, 什么是虚函数? 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离:用形象的语 ...
- 关于在C#中构造函数中调用虚函数的问题
在C#中如果存在类的继承关系,应避免在构造函数中调用虚函数.这是由于C#的运行机制造成的,原因如下: 新建一个类实例时,C#会先初始化该类(对类变量赋值,并将函数记在函数表中),然后再初始化父类.构造 ...
- 【C++】构造函数不能是虚函数
1 虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的.问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没 ...
- C++中构造函数能调用虚函数吗?(答案是语法可以,输出错误),但Java里居然可以
环境:XPSP3 VS2005 今天黑总给应聘者出了一个在C++的构造函数中调用虚函数的问题,具体的题目要比标题复杂,大体情况可以看如下的代码: class Base { public: Base() ...
- C++ Daily 《3》----构造函数可否是虚函数
C++ 中构造函数可否是虚函数? 绝不要!! 而且,在构造函数中调用虚函数也是不提倡的行为,因为会引发预想不到的结果. 因为,在 derived class 对象构造的过程中,首先调用的是基类的构造函 ...
- C++ 构造函数中调用虚函数
我们知道:C++中的多态使得可以根据对象的真实类型(动态类型)调用不同的虚函数.这种调用都是对象已经构建完成的情况.那如果在构造函数中调用虚函数,会怎么样呢? 有这么一段代码: class A { p ...
- C++反汇编第一讲,认识构造函数,析构函数,以及成员函数
C++反汇编第一讲,认识构造函数,析构函数,以及成员函数 以前说过在C系列下的汇编,怎么认识函数.那么现在是C++了,隐含有构造和析构函数 一丶认识构造函数 高级代码: class MyTest { ...
- C++中继承 声明基类析构函数为虚函数作用,单继承和多继承关系的内存分布
1,基类析构函数不为虚函数 #include "pch.h" #include <iostream> class CBase { public: CBase() { m ...
随机推荐
- codeforce364(div1.C). Beautiful Set
C. Beautiful Set time limit per test 1 second memory limit per test 256 megabytes input standard inp ...
- 如何改善win10录屏时声音降噪(消除杂音)
此文章是针对win10系统中安装Realtek声卡的麦克风出现杂音的设置办法 1. 打开win10的控制面板,找到"硬件和声音选项" 2. 进入"硬件和声音"选 ...
- vue 把字符串的所有=替换成&&&的方法
//把字符串中所有=换成&&& let reg=new RegExp('=','g')//g代表全部 let newMsg=JSON.stringify(msg).replac ...
- 如何使用Navicat连接宝塔面板上安装的mysql数据库?
运行环境描述 阿里云ECS 系统:CentOS Linux 7.4.1708 (Core) 宝塔面板: 6.9.0 数据库:MySQL 5.7.19 Navicat 远程连接 Navicat报错信息: ...
- Spring练习,使用注解的方式,完成模拟用户的正常登录。要求如下: 使用注解方式开发模拟用户的正常登录。
相关 知识 >>> 相关 练习 >>> 实现要求: 在该实践案例中,使用注解的方式,完成模拟用户的正常登录. 要求如下: 使用注解方式开发模拟用户的正常登录. 实现 ...
- 4.1.4 统计“锦途网”旅游线路平均价格,并采用尽可能多的方式将该价格赋给用户会话变量 @avg_short_price,并输出该变量
查看本章节 查看作业目录 需求说明: 统计"锦途网"旅游线路平均价格,并采用尽可能多的方式将该价格赋给用户会话变量 @avg_short_price,并输出该变量 在 MySQL ...
- Storm集群开启HA高可用
Storm开启HA高可用,包括Nimbus和UI开启两个及以上的进程. 基于已经安装好的Storm集群,开启关键节点角色的HA高可用. Storm安装请参考Storm集群安装Version1.0.1 ...
- Selenium_模拟键盘和鼠标操作(9)
模拟键盘键盘和鼠标操作主要使用到selenium的keys包,源码如下 class Keys(object): """ Set of special keys codes ...
- 移动端开发,rem单位妙用,自适应
其实我们都会,但是不会用 大家看到的都是,这样的,这样的. http://www.woshipm.com/ucd/24110.html http://www.w3cplus.com/css3/defi ...
- 闯祸了,生成环境执行了DDL操作《死磕MySQL系列 十四》
由于业务随着时间不停的改变,起初的表结构设计已经满足不了如今的需求,这时你是不是想那就加字段呗!加字段也是个艺术活,接下来由本文的主人咔咔给你吹. 试想一下这个场景 事务A在执行一个非常大的查询 事务 ...