每当创建一个交易对象,在审计日志中也要创建一笔适当记录。下面是一个看起来比较合理的做法: class Transaction { public: Transaction() { init(); } virtual void logTransaction() const { std::cout << "transaction" ;}; protected: private: void init() { logTransaction(); } }; class BuyTransaction : public Transaction { public: BuyTransaction():a(5){} virtual void logTransaction() const { std::cout << "BUytransaction"<< a;}; protected: private: int a; }; class SellTransaction : public Transaction { public: virtual void logTransaction() const; protected: private: }; inline void testItem9() { BuyTransaction b; } base class构造期间virtual函数绝不会下降到derived classes阶层。取而代之的是,对象的作为就像隶属base类型一样。 非正式的说法:在base class构造期间,virtual函数不是virtual函数。 由于base class 的构造函数的执行更早于derived class构造函数,当base class的构造函数执行时,derived class的成员变量尚未初始化。如果期间调用的virtual函数下降至derived class阶层, derived class的函数几乎必然取用local成员变量,而那些成员变量尚没有初始化。这会导致不明确行为和彻夜调试。 其实还有更根本的原因,在derived对象的base class 构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至base class,若使用运行期类型信息(runtime type information,例如dynamic_cast和typeid),也会把对象视为base class类型。本例中当Transaction构造函数正执行起来打算初始化“BuyTransaction对象内的base class成分”时,该对象的类型是Transaction。这个对象内的“BuyTransaction专属成分”尚未被初始化,所以面对这些,最安全的做法是视他们不存在。对象在derived class构造函数开始执行之前不会成为一个derived class对象。 相同道理也适用于析构函数。一旦derived class 析构函数开始执行, 对象内的derived class成员变量便呈现未定义值, 所以c++视他们仿佛不存在,进入base class析构函数后,对象就变成一个base class对象。 如何确保每次一有Transaction继承体系上的对象被创建,就会有适当版本的logTransaction被调用呢?在构造函数中调用virtual函数是一种错误的做法。 其他方案可以解决这个问题。一种做法是在Transaction内将logTransaction修改为non-virtual,然后要求derived class 构造 函数传递必要的信息给Transaction构造函数, 然后那个构造函数便可安全的调用non-virtual的logTransaction。像这样: class Transaction { public: explicit Transaction(const std::string &logInfo); void logTransaction(const std::string &logInfo) const; protected: private: }; Transaction::Transaction(const std::string &logInfo) { logTransaction(logInfo) } class BuyTransaction : public Transaction { public: BuyTransaction(parameters) : Transaction(createLogString(parameters)) { ...; } protected: private: static std::string createLogString(parameters); int a; }; 换句话说由于你无法使用virtual函数从base class向下调用,在构造期间,你可以藉由“令derived class将必要的构造信息向上传递至base class构造函数”替换之而加以弥补。 注意本例之BuyTransaction内的private static函数createLogString的运用。是的,比起在成员初始化列表里给予base class 所需数据。利用辅助函数创建一个值传给base class构造函数往往比较方便。令此函数为static,也就不可能意外指向“初值未成熟的BuyTransaction对象内尚未初始化的成员变量”。 在构造函数和析构函数期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造和析构的那层)。
|
请发表评论