编译环境: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】831. Masking Personal Information 解题报告(Python)

    [LeetCode]831. Masking Personal Information 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzh ...

  2. 分析一个简单的goroutine资源池

    分析一个简单的goroutine资源池 tunny. 从资源池中获取goroutine并进行处理的逻辑如下: tunny将goroutine处理单元封装为workWrapper,由此可以对gorout ...

  3. 动态规划题 HDU-1024

    http://acm.hdu.edu.cn/showproblem.php?pid=1024 Now I think you have got an AC in Ignatius.L's " ...

  4. Java中常见的转义字符

    转移字符对应的英文是escape character  , 转义字符串(Escape Sequence)字母前面加上捺斜线"\"来表示常见的那些不能显示的ASCII字符.称为转义字 ...

  5. 探索 dotnet core 为何在 Windows7 系统需要补丁的原因

    在一些 Windows 7 系统上,根据 dotnet 官方文档,需要安装上 KB2533623 补丁,才能运行 dotnet core 或 .NET 5 等应用.尽管非所有的设备都需要安装此,但这也 ...

  6. CGO快速入门

    1. 通过`improt "C"`语句开启CGO特性2. `/**/`中间是C代码,之后接 import "C" 如果存在空行 就会报错.could not d ...

  7. K210,yolo,face_mask口罩检测模型训练及其在K210,kd233上部署

    前段时间考研,再加上工作,时间很紧,一直没有更新博客,这几天在搞k210的目标检测模型,做个记录,遇到问题可以添加qq522414928或添加微信13473465975,共同学习 首先附上github ...

  8. 初识python 之 离线搭建pyhive环境(含python3安装)

    系统版本: centos6.5 python版本:python3.6.8 相关包存放目录:software 注意:以下操作需要用到root权限 安装python3 root操作 cd /lzh/sof ...

  9. 初识python: 模块定义及调用

    一.定义 模块:用来从逻辑上组织python代码(变量.函数.类.逻辑:实现一个功能),本质就是.py结尾的python文件(比如:文件名:test.py,对应的模块名:test) 包:用来从逻辑上组 ...

  10. JMeter_jmeter-plugins插件的安装使用

    一.安装JMter Plugins 1.官网下载 JMeter Plugins 的jar包 2. 将下载的jar包复制到 %JMETER_HOME%\lib\ext 目录下 3. 启动 JMeter ...