zoukankan      html  css  js  c++  java
  • effective C++ 条款 9:绝不在析构和构造函数中调用virtual函数

    每当创建一个交易对象,在审计日志中也要创建一笔适当记录。下面是一个看起来比较合理的做法:

    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(比起当前执行构造和析构的那层)。

  • 相关阅读:
    JavaScript设计模式与开发实践——读书笔记1.高阶函数(下)
    JavaScript设计模式与开发实践——读书笔记1.高阶函数(上)
    js 去除字符串中的空格
    js 运算符 || && 妙用
    判断一个js对象是不是数组
    Javascript中的异步
    js异步处理工作机制(setTimeout, setInterval)
    移动端html页面优化
    编写高效的jQuery代码
    JavaScript学习笔记 isPrototypeOf和hasOwnProperty使用区别
  • 原文地址:https://www.cnblogs.com/lidan/p/2321726.html
Copyright © 2011-2022 走看看