编译环境: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++ 构造函数、析构函数与虚函数的关系的更多相关文章

  1. 转 C++构造函数、析构函数、虚函数之间的关系

    C++构造函数.析构函数.虚函数之间的关系 1. 如果我们定义了一个构造函数,编译器就不会再为我们生成默认构造函数了.2. 编译器生成的析构函数是非虚的,除非是一个子类,其父类有个虚析构,此时的函数虚 ...

  2. C++中为什么构造函数不能是虚函数,析构函数是虚函数

    一, 什么是虚函数? 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离:用形象的语 ...

  3. 关于在C#中构造函数中调用虚函数的问题

    在C#中如果存在类的继承关系,应避免在构造函数中调用虚函数.这是由于C#的运行机制造成的,原因如下: 新建一个类实例时,C#会先初始化该类(对类变量赋值,并将函数记在函数表中),然后再初始化父类.构造 ...

  4. 【C++】构造函数不能是虚函数

    1 虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的.问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没 ...

  5. C++中构造函数能调用虚函数吗?(答案是语法可以,输出错误),但Java里居然可以

    环境:XPSP3 VS2005 今天黑总给应聘者出了一个在C++的构造函数中调用虚函数的问题,具体的题目要比标题复杂,大体情况可以看如下的代码: class Base { public: Base() ...

  6. C++ Daily 《3》----构造函数可否是虚函数

    C++ 中构造函数可否是虚函数? 绝不要!! 而且,在构造函数中调用虚函数也是不提倡的行为,因为会引发预想不到的结果. 因为,在 derived class 对象构造的过程中,首先调用的是基类的构造函 ...

  7. C++ 构造函数中调用虚函数

    我们知道:C++中的多态使得可以根据对象的真实类型(动态类型)调用不同的虚函数.这种调用都是对象已经构建完成的情况.那如果在构造函数中调用虚函数,会怎么样呢? 有这么一段代码: class A { p ...

  8. C++反汇编第一讲,认识构造函数,析构函数,以及成员函数

    C++反汇编第一讲,认识构造函数,析构函数,以及成员函数 以前说过在C系列下的汇编,怎么认识函数.那么现在是C++了,隐含有构造和析构函数 一丶认识构造函数 高级代码: class MyTest { ...

  9. C++中继承 声明基类析构函数为虚函数作用,单继承和多继承关系的内存分布

    1,基类析构函数不为虚函数 #include "pch.h" #include <iostream> class CBase { public: CBase() { m ...

随机推荐

  1. 【LeetCode】102. Binary Tree Level Order Traversal 二叉树的层序遍历 (Python&C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 BFS DFS 日期 题目地址:https://lee ...

  2. CF 593B Anton and Lines(水题)

    题意是给你n条直线,和x1,x2;问 在x1,x2之间(不包括在x1,x2上) 存不存在任意两条线的交点. 说思路,其实很简单,因为给的直线的条数很多,所以无法暴力求每两条直线的交点,那么就求每条直线 ...

  3. Anti-prime Sequences

    Anti-prime Sequences Time Limit: 3000MS   Memory Limit: 30000K Total Submissions: 3355   Accepted: 1 ...

  4. 如何利用Python实现Office在线预览

    目前,市场对于Office在线预览功能的需求是很大的.对于我们用户本身来说,下载Office文件后再实现预览是极其不方便的,何况还有一些不能打开的专业文档.压缩文件等.此时,能提供在线预览服务的软件就 ...

  5. static,final,volatile

    static  静态修饰关键字,可以修饰 变量,程序块,类的方法:[被 static 修饰的方法和属性只属于类不属于类的任何对象.] 当你定义一个static的变量的时候jvm会将将其分配在内存堆上, ...

  6. Feign动态调用,结合Ribbon

    代码如下,三种方法: import org.springframework.beans.factory.annotation.Autowired;import org.springframework. ...

  7. Accelerating Deep Learning by Focusing on the Biggest Losers

    目录 概 相关工作 主要内容 代码 Accelerating Deep Learning by Focusing on the Biggest Losers 概 思想很简单, 在训练网络的时候, 每个 ...

  8. Deepin20系统安装Nvidia驱动

    Deepin20系统安装Nvidia驱动 系统设备配置信息如下: 电脑型号:华硕天选air[ASUS-FX516P] 显卡型号:RTX 3070 移动版独显 处理器型号: 11th Gen Intel ...

  9. CH7511|LT7211|PS8625替代方案 CS5211 设计EDP转LVDS优势方案原理图+PCB板设计

    CH7511|LT7211|PS8625这三款都是专门用于设计EDP转LVDS转接板或者屏转换方案板,CH7511.LT7211.PS8625目前这几款都是出于缺货状态,台湾瑞奇达Capstone 新 ...

  10. MySQL数据操作与查询笔记 • 【第4章 SELECT 数据查询】

    全部章节   >>>> 本章目录 4.1 select 选择列表 4.1.1 select 基本结构 4.1.2 选择列表 4.2 MySQL 运算符 4.2.1 MySQL ...