However,是不是只要在基类中使用了virtual函数就一定能够实现这种动态调用呢?是否只要在base class中定义了虚函数,那么在通过基类指针指向子类的时候就一定会调用子类实现的对应的virtual函数呢?
我们来实际验证一下:
假设有一个class继承体系,用来模拟股市交易如买进、卖出等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志中需要创建一笔适当的记录。下面是一个看似颇为合理的做法:
结果并没有调用各个子类对应的日志记录函数logTransaction。调用的都是基类的函数!
为什么会出现这种结果呢?原因如下:
当我们执行New BuyTrasaction()的时候,会调用基类和本来的构造函数,但是base class的构造函数会先于derived class的构造函数被调用,当base class构造函数执行时derived class的成员变量尚未初始化(因为还未调用其构造函数啊),如果此时在base class的构造函数里面调用了base class定义的virtual函数,如果这个virtual函数是子类实现的那个的话,就会出现“使用对象内部尚未初始化的成分”的糟糕情况,这是C++禁止的,所以此时调用的virtual只可能是base class中的那个virtual 函数,不是子类的那个。
该道理同样适用于析构函数,如果在base class中调用了virtual函数的话,因为对象释放的时候会先调用子类的析构函数,然后再调用基类的析构函数,当调用基类的析构函数的时候,子类中的成员变量已经被释放了,不存在了,所以执行的还是基类中的virtual函数。
如何来解决这个问题呢?有两个方案:
方案一:在base class中将日志记录函数logTransaction定义为纯虚函数,其构造函数不调用这个函数,然后在子类中实现这个函数,并在每个子类的构造函数中调用这个函数!
这种方法虽然能够实现其功能,however,不是一种好的“代码复用”方式,因为需要在每个子类中都要定义和调用同一个日志记录函数。
当然,这种方法也尤其不得不用的case:假设B是A的子类,且initilaize是他们的虚成员函数。难道B构造函数不可以调用自己的initialize函数?这种case下,这个方案提供了一种可定制化对象初始化的方式。
方案二:在base class中将logTransaction函数改为非virtual函数,然后要求子类构造函数传递必要信息给Transaction构造函数,而后那个构造函数便可以安全地调用非virtual函数logTransaction了!
看下各自的实现代码:
方案一:
方案二的代码: