编译环境: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. Wiener Filtering

    目录 基本 滤波的推导 特别的情况 特别的例子 Signals, Systems and Inference, Chapter 11: Wiener Filtering (mit.edu) 基本 在图 ...

  2. Robust Pre-Training by Adversarial Contrastive Learning

    目录 概 主要内容 代码 Jiang Z., Chen T., Chen T. & Wang Z. Robust Pre-Training by Adversarial Contrastive ...

  3. MMD

    目录 概 主要内容 定义 MMD for kernel function classes 一个无偏统计量 MMD test Borgwardt K., Gretton A., Rasch M., Kr ...

  4. WPF之AvalonEdit实现MVVM双向绑定

    AvalonEdit简介 AvalonEdit是基于WPF开发的代码显示控件,默认支持多种不同语言的关键词高亮,并且可以自定义高亮配置.所以通过AvalonEdit可以快速开发出自己想要的代码编辑器. ...

  5. JSON.parse 和 JSON.stringify 详解

    JSON格式,(简写JavaScript Object Notation),是一种用于数据交换的文本格式,书写简单. 基于JavaScript原生语法,能够序列化对象.数组.数值.字符串.布尔值和 n ...

  6. Notepad++在线安装使用JSON插件

    1.介绍 JSON Viewer是Notepad++的JSON插件, Notepad++的Plugins Admin可以安装管理插件, 它不仅可以格式化JSON字符串, 还可以打开JSON查看器查看结 ...

  7. CSS基础 精灵图的使用

    使用步骤1.创建盒子     <div class="one"></div>   2.使用PxCook量取图标大小,将图标的宽高设置成为盒子的宽高      ...

  8. Hadoop 之 高可用不自动切换(ssh密钥无效 Caused by: com.jcraft.jsch.JSchException: invalid privatekey )

    案例 在安装hadoop ha之后,验证HDFS高可用时,怎么都不能实现自动切换.查看zkfc日志发现错误信息如下: WARN org.apache.hadoop.ha.SshFenceByTcpPo ...

  9. wordpress搭建网站更改域名后打开网页排版显示错乱解决办法

    发生的原因: 我本来已经搭建好了网站,也测试了没问题.后来更改了网站的域名,出现了这种情况. 解决办法: 需要修改数据库的options表里面的 siteurl 和 home 这两个表的内容为最新的域 ...

  10. SGU140. Integer Sequences

    https://codeforces.com/problemsets/acmsguru/problem/99999/140 n元同余方程的求解 对于任意二元我们可以替换成kgcd(a,b),不断迭代下 ...