构造函数与析构函数中不调用虚函数

原创 2016年06月02日 00:44:57

本文参考《effective C++》第九条款
在C++中,提倡不能在构造函数和析构函数中调用虚函数。
这是为什么呢?

首先,我们先回顾一下C++虚函数的作用。 虚函数的引入是c++运行时多态的体现,通过调用虚函数可以在运行程序时实现动态绑定,体现了面向对象编程多态的思想。


那为何提倡不能在构造函数与析构函数中不能调用虚函数。接下来我们通过代码分析在构造函数或者虚构函数中调用虚函数是否可行。

假设我们有两种商品A, B。 我们要记录着两种商品的销售记录,每当销售一种商品时,我们就要记录下来。

class item {
public:
    item();
    virtual void saleRecord() const=0;  //销售记录,纯虚函数
    ...
};

item::item()
{
    ...
    virtual void saleRecord();
    ...
}

class itemA :  public item {
public:
    itemA();
    virtual void saleRecord();
    ...
};

class itemB : public item {
public:
    itemB();
    virtual void saleRecord();
    ...
};

我们执行如下代码:

itemB b;

一个derived class B 对象会被创建, 在调用构造函数itemB() 之前, 基类的构造函数会首先被调用,即:基类成分会在派生类特有成分被构造之前被构造。 而在基类的构造函数中又调用了虚函数saleRecord(), 在此,你认为基类构造函数中所调用的虚函数saleRecord的实现版本是哪一个呢,是基类的实现版本还是派生类B的实现版本呢?

我们会很自然的认为, 现在构建的是itemB的对象, 构造函数中调用的当然是派生类B的实现版本。 其实我们可以通过运行程序确认基类构造函数中调用的虚函数实现版本是基类所有的,而不是派生类的实现版本。即使我们现在创建的是一个派生类。

原因其实很简单,在创建派生类的时候,基类先于派生类被构造,编译器或者程序其实目光是很短浅的或者说是很现实的,在调用基类构造函数的时候,它并不知道你最终是要创建一个基类还是派生类, 它只要把现在手头上的工作做好——创建一个基类。 对编译器来说,此时所有可见的信息包括data member 以及data function 都是基类所有的,它并不知道派生类的任何信息, 而且它所做的行为也只是初始化派生类空间专属于基类的那一部分, 不会越界。用一句话总结: 对象在调用什么构造函数的时候,它就是什么对象,他并不会像先知一样能看的更远。
以之前的代码为例。 创建派生类的时候, 基类首先被创建, 此时它只是一个基类,它的所见所为都完完全全跟创建一个基类对象一样,并不会下降到派生类。


我们可以极端一点,假设基类在创建的时候可以调用虚函数的派生类实现版本(其实不可能)。 此时,又会发生什么呢。从意图上讲,我们要调用虚函数的不同版本,从根本上讲,是要对不同的数据进行操作, 这样函数才有意义。比如说基类的虚函数版本是对基类的数据进行操作, 派生类的虚函数版本是对派生类的虚函数进行操作。

然而,我们不要忽略一点,假设基类可以调用虚函数的派生类实现版本,那么我们操控的数据则是派生类所有的数据, 此时,派生类的构造函数还没有调用,派生类的数据成员也不会有相应的初始化,这时候对派生类的数据成员操作完全是无意义的,从程序崩溃到机器冒烟都有可能。

对于析构函数, 与构造函数是几乎一样的,不能调用虚函数。

从上面的分析可以得出两点结论:
1. 构造函数或者析构函数调用虚函数并不会发挥虚函数动态绑定的特性,跟普通函数没区别。
2. 即使构造函数或者析构函数如果能成功调用虚函数, 程序的运行结果也是不可控的

相关文章推荐

能不能在构造函数和析构函数中调用虚函数?

可以,但是达不到想要的效果,应该尽可能避免在构造函数和析构函数中调用虚函数。 class base{ public: base(){ cout...

在构造函数/析构函数中调用虚函数

先看一段在构造函数中直接调用虚函数的代码: 1 #include 2 3 class Base 4 { 5 public: 6 Base() { Foo(); } ...

C++箴言:避免构造或析构函数中调用虚函数

如果你已经从另外一种语言如C#或者Java转向了C++,你会觉得,避免在类的构造函数或者析构函数中调用虚函数这一原则有点违背直觉。但是在C++中,违反这个原则会给你带来难以预料的后果和无尽的烦恼。 ...

为什么析构函数可以为virtual型,而构造函数则不能?

构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。不建议在构造函数和析构函数里面调用虚函数。构造函数不能声明为虚函数的原因是:解释一:所谓虚函数就是多态情况下只执行一个。...
  • will130
  • will130
  • 2015年10月11日 13:55
  • 807

C++构造函数和析构函数中抛出异常的注意事项

从语法上来说,构造函数和析构函数都可以抛出异常。但从逻辑上和风险控制上,构造函数和析构函数中尽量不要抛出异常,万不得已,一定要注意防止资源泄露。1.构造函数中抛出异常在C++构造函数中,既需要分配内存...

《Effective C++ 》条款9:永远不要在构造函数或析构函数中调用虚函数

我想以重复本文的主题开篇:不要在类的构造或者析构函数中调用虚函数,因为这种调用不会如你所愿,即使成功一点,最后还会使你沮丧不已。如果你以前是一个Java或者C#程序员,请密切注意本节的内容-这正是C+...
  • hxz_qlh
  • hxz_qlh
  • 2013年11月02日 21:55
  • 6329

C++:构造函数和析构函数能否为虚函数

C++:构造函数和析构函数能否为虚函数?简单回答是:构造函数不能为虚函数,而析构函数可以且常常是虚函数。(1) 构造函数不能为虚函数让我们来看看大牛C++之父 Bjarne Stroustrup 在《...
  • xhz1234
  • xhz1234
  • 2010年11月06日 00:36
  • 12292

C++中构造函数和析构函数避免调用虚函数的问题

一、构造函数避免调用虚函数的问题 在构造函数中调用虚成员函数,虽然这是个不很常用的技术,但研究一下可以加深对虚函数机制及对象构造过程的理解。这个问题也和一般直观上的认识有所差异。先看看下面的两个类定...

C++ 中预处理Pragma的理解

在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的...

机器学习-线性回归-多维度特征变量

1. 假设函数之前的几篇文章里面,我们都只是介绍了单维特征变量的线性回归模型,比如预测房价的时候,我们只用了房子的面积这个维度。接下来我们会去研究多个维度的线性回归模型还是从预测房价这个例子入手,假设...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:构造函数与析构函数中不调用虚函数
举报原因:
原因补充:

(最多只允许输入30个字)