编译环境: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. 自动化集成:Docker容器入门简介

    前言:该系列文章,围绕持续集成:Jenkins+Docker+K8S相关组件,实现自动化管理源码编译.打包.镜像构建.部署等操作:本篇文章主要描述Docker基础用法. 一.Docker简介 1.基础 ...

  2. Java Web程序设计笔记 • 【第7章 会话跟踪技术】

    全部章节   >>>> 本章目录 7.1 会话跟踪技术概述 7.1.1 为什么需要会话跟踪 7.1.2 会话的状态和跟踪 7.1.3 会话跟踪技术 7.1.4 会话跟踪工作流程 ...

  3. 利用 jQuery 操作页面元素的方法,实现电商网站购物车页面商品数量的增加和减少操作,要求单项价格和总价随着数量的改变而改变

    查看本章节 查看作业目录 需求说明: 利用 jQuery 操作页面元素的方法,实现电商网站购物车页面商品数量的增加和减少操作,要求单项价格和总价随着数量的改变而改变 当用户单击"+" ...

  4. 编写Java程序,通过给定可变参数方法,计算任意给定的多个int类型数据之和

    返回本章节 返回作业目录 需求说明: 通过给定可变参数方法,计算任意给定的多个int类型数据之和. 实现思路: 定义可变形参方法,参数类型是int类型. 定义变量 sum 接受最终的和. 通过 for ...

  5. 编写Java程序,实现控制台版的省市联动

    返回本章节 返回作业目录 需求说明: 系统显示用户所有直辖市的名称,用户在控制台输入直辖市的名称,即可在控制台中显示该直辖市所管辖的所有区的名称. 实现思路: 创建省市联动类ProvincialLin ...

  6. 为什么说DI解耦

    为什么说IOC/DI(控制反转/依赖注入)降低耦合 public class HomeController : Controller { private readonly IStudentRepons ...

  7. PHP 中的回调函数

    回调函数就是在主进程执行过程中,满足某个条件后,跳转到预先设置好的函数中去执行的一种函数. 举例说明: 张三到一个水果店买苹果,刚好苹果卖完了,于是张三在店员那里留下了自己的电话,一个小时后店里有货了 ...

  8. 基于CentOS7.x gitlab环境搭建,卸载,汉化 --卸载篇

    gitlab环境搭建,卸载,汉化 --卸载篇 完全卸载 停止gitlab gitlab-ctl stop 卸载gitlab rpm -e gitlab-ce 查看gitlab进程  ps aux | ...

  9. js 动态设置键值对数组 ,类似于 java 的Map 类型

    1.前言 我想设置一个数据  var json = {a1 :1  , a2 :2  , a3 :3  .....} 这样的动态数据 ,怎么写呢? 2.正确写法 var json = []; for ...

  10. Java 简单操作hdfs API

    注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6632047118376780295/ 启动Hadoop出现问题:datanode的clusterID 和 name ...