第八章:不要在构造和析构函数中使用虚函数

前言

本文将讲解一个新手C++程序员经常会犯的错误 - 在构造/析构函数中使用虚函数,并分析错误原因所在以及规避方法。

错误起因

首先,假设我们以一个实现交易的类为父类,然后一个实现买的类,一个实现卖的类为其子类。

这三个类的对象初始化过程中,都需要完成注册的这么一件事情 (函数)。然而,各自注册的具体行为是不同的。

有些人会写出以下这样的代码:

 1 class Transaction {
 2 public:
 3     Transaction();    // 父类构造函数
 4     //......
 5 private:
 6     //......
 7     void logTransaction() const;    // 父类的注册函数
 8     //......
 9 };
10
11 Transaction::Transaction() {
12     //......
13     logTransaction();    // 父类构造函数调用类内部成员函数
14     //......
15 }
16
17 class BuyTransaction : public Transaction {
18 private:
19     //......
20     void logTransaction() const;    // 子类一的注册函数
21     //......
22 };
23
24 class SellTransaction : public Transaction {
25 private:
26     //......
27     void logTransaction() const;    // 子类二的注册函数
28     //......
29 };

在这段代码中,编写者认为,子类会继承父类的构造函数,而继承之后,不同的子类又会调用他们自己的实现的注册函数。

这是错误的。

因为在子类调用父类的构造函数期间,子类类型是其父类类型,这个时候执行父类的构造函数其内部调用的注册函数也是父类版本的,而非子类版本的。

错误的解决方案

由于上面所说的错误,一些人想到了虚函数解决方案:

 1 class Transaction {
 2 public:
 3     Transaction();    // 父类构造函数
 4     //......
 5 private:
 6     //......
 7     virtual void logTransaction() const = 0;    // 注册函数声明为虚函数
 8     //......
 9 };
10
11 Transaction::Transaction() {
12     //......
13     logTransaction();    // 父类构造函数调用类内部成员函数
14     //......
15 }
16
17 class BuyTransaction : public Transaction {
18 private:
19     //......
20     virtual void logTransaction() const;    // 子类一的注册函数
21     //......
22 };
23
24 class SellTransaction : public Transaction {
25 private:
26     //......
27     virtual void logTransaction() const;    // 子类二的注册函数
28     //......
29 };

很遗憾,这么做还是行不通。一旦你构造一个子类对象,链接器会提示你链接失败 - 调用未定义的纯虚函数。这说明子类构造函数使用的注册函数依然是父类的。

很多人开始吐槽C++(第一次碰到这种情况的时候我也是),觉得这样的设定很奇葩。

但其实C++这么设定是有原因的:在父类构造函数执行期间,子类的成员变量并没有初始化完全,因此在此阶段调用子类的成员函数应当被禁止。

正确的解决方案

首先,至此我们要明确:不能在构造函数中使用虚函数了,这么做根本无法实现多态。

然后,采用什么办法能够做到在父类构造函数中以调用成员函数的方式完成初始化呢?

本例中,正确的做法是在父类中将注册函数取消其虚函数声明,而在子类的构造函数中,自行调用父类构造函数并传递进子类对象部分相关信息。当父类构造函数获取到子类部分传递进来的信息之后,就能根据传递进来的信息,有选择的调用相应注册函数。

请看代码示例:

 1 class Transaction {
 2 public:
 3     explicit Transaction(const std::string & logInfo);        // 父类构造函数
 4     //......
 5 private:
 6     //......
 7     void logTransaction(const std::string & logInfo);    // 改为非虚函数
 8     //......
 9 };
10
11 Transaction::Transaction(const std::string & logInfo) {
12     //......
13     // 父类构造函数调用类内部成员函数 注册函数根据不同的logInfo做出不同的初始化处理
14     logTransaction(logInfo);
15     //......
16 }
17
18 class BuyTransaction : public Transaction {
19 public:
20     BuyTransaction(/*parameters*/);    // 子类构造函数
21     //......
22 private:
23     //......
24     // 采用静态函数生成子类部分初始化信息,确保不会使用到子类中未完成初始化的数据。
25     static std::string createLogString(/*parameters*/);
26     //......
27 };
28
29 // 子类构造函数定义
30 BuyTransaction :: BuyTransaction(/*parameters*/) : Transaction(createLogString(/*parameters*/))
31 {
32     //......
33 }

小结

1. 请仔细体会本文的几个类设计过程中所体现出的面向对象思想。

2. 本文焦点是构造函数,但同样适用于析构函数。

时间: 2024-08-04 14:16:05

第八章:不要在构造和析构函数中使用虚函数的相关文章

EC笔记,第二部分:9.不在构造、析构函数中调用虚函数

9.不在构造.析构函数中调用虚函数 1.在构造函数和析构函数中调用虚函数会产生什么结果呢? #include <iostream> using namespace std; class cls1{ public: cls1(){ newMake(); }; ~cls1(){ deleteIt(); }; virtual void newMake(){ cout<<"cls1 make"<<endl; } virtual void deleteIt()

不要在构造和析构函数中使用虚函数

前言 本文将讲解一个新手 C++ 程序员经常会犯的错误 - 在构造/析构函数中使用虚函数,并分析错误原因所在以及规避方法. 错误起因 首先,假设我们以一个实现交易的类为父类,然后一个实现买的类,一个实现卖的类为其子类. 这三个类的对象初始化过程中,都需要完成注册的这么一件事情 (函数).然而,各自注册的具体行为是不同的. 有些人会写出以下这样的代码: 1 class Transaction { 2 public: 3 Transaction(); // 父类构造函数 4 //...... 5 p

条款9:绝不要在构造以及析构函数中调用虚函数

在构造以及析构函数期间不要调用virtual函数,因为这类调用从不下降到derived class中.例如说下面这个例子: 1 class Transaction{ 2 public: 3 Transaction(); 4 virtual void logTransactions()s const = 0; 5 //... 6 }; 7 Transaction::Transaction() 8 { 9 //... 10 logTransaction(); 11 } 12 class BuyTra

C++ 笔记(二) —— 不要在构造和析构函数中调用虚函数

ilocker:关注 Android 安全(新手) QQ: 2597294287 1 class Transaction { //所有交易的 base class 2 public: 3 Transaction(); 4 virtual void logTransaction() const = 0; //做出一份因类型不同而不同的日志记录 5 … 6 } 7 Transaction::Transaction() { 8 … 9 logTransaction(); 10 } derived cl

绝不在构造和析构函数中调用 virtual 函数

看下面的这段代码,问 print调用的是基类还是派生类的版本? 答案是 基类... 可能大家会很惊讶,print不是virtual function 吗?为什么不是调用派生类的版本呢? 首先,当定义一个派生类的对象的时候, 由于 base class 构造函数的执行更早于 derived class构造函数, 所以当 base class constructor 调用的时候,派生类的成员尚未初始化(说明,这个时候真正的 虚函数表尚未完全初始化). 如果这个时候调用 派生类的函数(可能使用未初始化

构造函数和析构函数中的虚函数

构造派生类对象时首先运行基类构造函数初始化对象的基类部分.在执行基类构造函数时,对象的派生类部分是未初始化的.实际上,此时对象还不是一个派生类对象. 撤销派生类对象时,首先撤销它的派生类部分,然后按照与构造顺序的逆序撤销它的基类部分. 在这两种情况下,运行构造函数或析构函数的时候,对象都是不完整的.为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化.在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待. 构造或析构期间的对象类型对虚函数的绑定有影响. 如果在构造函数或析

C++进阶--构造函数和析构函数中的虚函数

//############################################################################ /* 任何时候都不要在构造函数或析构函数中调用虚函数 */ class dog { public: string m_name; dog(string name) {m_name = name; bark();} virtual void bark() { cout<< "Woof, I am just a dog "

在构造函数和析构函数中调用虚函数------新标准c++程序设计

在构造函数和析构函数中调用虚函数不是多态,因为编译时即可确定调用的是哪个函数.如果本类有该函数,调用的就是本类的函数:如果本类没有,调用的就是直接基类的函数:如果基类没有,调用的就是间接基类的函数,以此类推.例如: #include<iostream> using namespace std; class A { public: virtual void hello(){cout<<"A::hello()"<<endl;} virtual void

[C++]在构造函数及析构函数中调用虚函数

(ISO/IEC 14882:2011 section 12.7.4): Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2).When a virtual function is called directly or indirectly from a constructor or from a destructor, inc