【问题标题】:C++ Type changes during derived class construction - virtual function question派生类构造期间的 C++ 类型更改 - 虚函数问题
【发布时间】:2011-09-19 15:11:20
【问题描述】:

删除期间C++类型改变

我了解到,当您构造派生类型时,类型会根据调用的构造函数而改变。因此,如果您创建派生对象并使用基指针调用虚函数,通常它会映射到派生类中的实现。但是,如果您在基类构造函数中调用虚函数,它将使用基类实现,因为在该函数中对象的类型在技术上是基类的类型。例如(临时代码,如果无法编译,请见谅):

class Base { 
    Base()
    {
        std::cerr << "Base Constructor.";
        func();
    }

    virtual void func() {
        std::cerr << "Func base called." << std::endl;
    }
};

class Derived : public Base {
    Derived()
    {
        std::cerr << "Derived Constructor.";
        func();
    }

    void func() {
        std::cerr << "Func derived called." << std::endl;
    }
};

int main() {
    Derived* d = new Derived;
    delete d;
}

应该输出:

Base Constructor.
Func base called.
Derived Constructor.
Func derived called.

首先,这总是正确的还是依赖于实现?

如果我使用 RTTI 和 typeinfo,那么在 base 中打印的类型是否实际上是 base 的类型,或者这更像是一种不成文的规则之类的情况?

考虑到这一点从构造函数调用虚函数是否危险,或者只要你知道自己在做什么就安全吗?

【问题讨论】:

    标签: c++


    【解决方案1】:

    为了简洁明了,你可以有一个规则:

    在构造函数和析构函数中禁用了虚拟机制

    基类中的虚函数调用将始终调用该函数的基类版本,派生类中同样会调用该函数的派生类版本。

    首先,这总是正确的还是依赖于实现?

    是的,这总是正确的。这与实现无关。

    如果我使用 RTTI 和 typeinfo,那么 base 中打印的 type 是否真的是 base 的 type?

    是的,它将是 Base 的;当您在基类构造函数中时,派生对象甚至不存在。

    考虑到这一点,从构造函数调用虚函数是否危险,或者只要你知道自己在做什么就安全吗?

    不,只要你理解它背后的语义,从构造函数调用虚函数并不危险。


    This C++ FAQ 应该适合您阅读。

    【讨论】:

      【解决方案2】:

      定义明确。

      [n3290: 12.7/4]: 成员函数,包括虚函数 (10.3),可以在构造或销毁(12.6.2)期间调用。 当一个虚函数被调用时直接或间接从 构造函数或来自析构函数,包括在构造过程中或 销毁类的非静态数据成员,以及对象 调用适用的是正在构建的对象(称为x)或 破坏,调用的函数是 构造函数或析构函数的类,而不是一个覆盖它的类 更多派生类。 如果虚函数调用使用显式 类成员访问(5.2.5)和对象表达式是指 x 的完整对象或该对象的基类子对象之一,但 不是x 或其基类子对象之一,行为未定义。

      【讨论】:

        【解决方案3】:

        Scott Meyers 有一篇很棒的文章。它来自他的《Effective C++》一书。 文章可以在以下位置找到: Never Call Virtual Functions during Construction or Destruction

        它还讨论了另一种实现方式。

        最近我遇到了类似的问题,我通过这种方式解决了:

        class EthernetFrame
        {
        protected:
          /** ctor to be called from derived classes */
          EthernetFrame(unsigned inPayloadLength)
          {
            calculatePadBytes(inPayloadLength);
          }
        
        private:
          /** calculates needed required PadBytes for Frames < 64B
           * @param inPayloadLength we need to know the length of the actual L3 frame
           */
          void calculatePadBytes(unsigned inPayloadLength);
        
        };
        
        class IPv4Frame : public EthernetFrame
        {
        public:
          /** create empty IPv4 packet */
          IPv4Frame() :
            EthernetFrame(cIPv4_MINIMUM_LENGTH)
          {};
          // IPv4 header + trailer in bytes
          unsigned cIPv4_MINIMUM_LENGTH;
        protected:
          /** ctor to be called from derived classes */
          IPv4Frame(unsigned inPayloadLength) :
            EthernetFrame(cIPv4_MINIMUM_LENGTH+inPayloadLength)
          {};
        
        };
        

        【讨论】:

        • 我不明白你的 sn-p 与多态函数调用的相关性。
        • 代码 sn-p 不涉及函数的多态性。它是从链接文章派生的实现,并应用于我自己的问题。
        • 其实这很贴切,这就是我 +1 的原因。我在阅读了那本书的那一部分后问了这个问题(它是什么,第 20 项?)——我只是想走得更远,问一些后续问题。他的 sn-p 是 Myers 建议的解决方案的一个很好的应用。无论如何,我很欣赏真实世界的样本:)
        猜你喜欢
        • 1970-01-01
        • 2020-02-28
        • 2018-09-24
        • 1970-01-01
        • 2012-12-19
        • 2011-09-27
        • 2023-04-07
        • 2012-12-07
        • 1970-01-01
        相关资源
        最近更新 更多