派生类构造期间的C ++类型更改 - 虚函数问题

时间:2011-09-19 15:11:21

标签: c++

删除期间的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,基数中打印的类型实际上是基数类型,还是更多的是一种不成文的规则呢?

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

3 个答案:

答案 0 :(得分:8)

为了简单起见,您可以制定规则:

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

Base类中的虚函数调用将始终调用函数的基类版本,派生类中的相同函数会导致调用函数的Derived类版本。

  

首先,这是真的还是依赖于实现?

是的,这总是如此。这不依赖于实现。

  

如果我使用RTTI和typeinfo,那么打印在基础中的类型实际上是基数类型吗?

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

  

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

只要您理解其背后的语义,从构造函数调用虚函数就没有危险。


This C++ FAQ 应该是一本很好的读物。

答案 1 :(得分:2)

它定义明确。

  

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

答案 2 :(得分:1)

斯科特迈耶斯有一篇很好的文章。这是他的书“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)
  {};

};