这是一个很容易让人迷惑的principle!按照我们对C++多态性的理解:定义基类中某个函数为虚函数是为了允许用基类的指针来调用子类的这个函数。通过virtual函数实现程序运行时候的动态调用。
However,是不是只要在基类中使用了virtual函数就一定能够实现这种动态调用呢?是否只要在base class中定义了虚函数,那么在通过基类指针指向子类的时候就一定会调用子类实现的对应的virtual函数呢?
我们来实际验证一下:
假设有一个class继承体系,用来模拟股市交易如买进、卖出等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志中需要创建一笔适当的记录。下面是一个看似颇为合理的做法:
[cpp] view plaincopyprint?
#include
using namespace std;
class Transaction //所有交易的基类
{
public:
Transaction();
virtual void logTransaction() const //交易记录
{
cout<<"Log in Transaction"<
}
};
Transaction::Transaction()
{
logTransaction();
}
class BuyTransaction : public Transaction
{
public:
virtual void logTransaction() const
{
cout<<"Log in BuyTransaction"<
}
};
class SellTransaction : public Transaction
{
public:
virtual void logTransaction() const
{
cout<<"Log in SellTransaction"<
}
};
int main()
{
Transaction *action;
action = new BuyTransaction();
action = new SellTransaction();
}
我们希望每当有一个交易(ButTransaction or SellTransaction)创建的时候,就自动记录此次交易,因此我们在基类的构造函数里面调用日志记录函数。能否真正实现记录不同的交易的功能呢?我们运行程序:
结果并没有调用各个子类对应的日志记录函数logTransaction。调用的都是基类的函数!
为什么会出现这种结果呢?原因如下:
当我们执行New BuyTrasaction()的时候,会调用基类和本来的构造函数,但是base class的构造函数会先于derived class的构造函数被调用,当base class构造函数执行时derived class的成员变量尚未初始化(因为还未调用其构造函数啊),如果此时在base class的构造函数里面调用了base class定义的virtual函数,如果这个virtual函数是子类实现的那个的话,就会出现“使用对象内部尚未初始化的成分”的糟糕情况,这是C++禁止的,所以此时调用的只可能是base class中的那个virtual 函数,不是子类的那个。
该道理同样适用于析构函数,如果在base class中调用了virtual函数的话,因为对象释放的时候会先调用子类的析构函数,然后再调用基类的析构函数,当调用基类的析构函数的时候,子类中的成员变量已经被释放了,不存在了,所以执行的还是基类中的virtual函数。
如何来解决这个问题呢?有两个方案:
方案一:在base class中将日志记录函数logTransaction定义为纯虚函数,其构造函数不调用这个函数,然后在子类中实现这个函数,并在每个子类的构造函数中调用这个函数!
这种方法虽然能够实现其功能,however,不是一种好的“代码复用”方式,因为需要在每个子类中都要定义和调用同一个日志记录函数,what a fucking waste!
方案二:在base class中将logTransaction函数改为非virtual函数,然后要求子类构造函数传递必要信息给Transaction构造函数,而后那个构造函数便可以安全地调用非virtual函数logTransaction了!
看下各自的实现代码:
方案一:
[cpp] view plaincopyprint?
#include
using namespace std;
class Transaction
{
public:
Transaction();
virtual void logTransaction() = 0;
};
Transaction::Transaction(){}
class BuyTransaction : public Transaction
{
public:
virtual void logTransaction()
{
cout<<"Log in BuyTransaction"<
}
BuyTransaction()
{
logTransaction();
}
};
class SellTransaction : public Transaction
{
public:
virtual void logTransaction()
{
cout<<"Log in SellTransaction"<
}
SellTransaction()
{
logTransaction();
}
};
int main()
{
Transaction *action;
action = new BuyTransaction();
action = new SellTransaction();
}
执行结果:
方案二的代码:
[cpp] view plaincopyprint?
#include
#include
using namespace std;
class Transaction
{
public:
Transaction(const string& logInfo);
void logTransaction(const string& logInfo) const; //non-virtual
static string createLogString(string parameters)
{
return parameters;
}
};
Transaction::Transaction(const string& logInfo)
{
logTransaction(logInfo);
}
void Transaction::logTransaction(const string& logInfo) const
{
cout<<"log " + logInfo + " info successfully!"<
}
class BuyTransaction : public Transaction
{
public:
BuyTransaction(string parameters): Transaction(createLogString(parameters)) {}
};
class SellTransaction : public Transaction
{
public:
SellTransaction(string parameters): Transaction(createLogString(parameters)) {}
};
int main()
{
Transaction *action;
action = new BuyTransaction("buy transation");
action = new SellTransaction("sell transaction");
}
此种方式相对较为灵活,代码复用性比较好!本代码在《effective C++》的基础上做了些许优化!
阅读(1126) | 评论(0) | 转发(0) |