在C ++中使用多态进行初始化的预期行为是什么?

时间:2011-04-09 17:44:21

标签: c++ polymorphism

这应该是一个简单的100级问题,但是我在项目中看到了一些我没想到的东西,网络搜索让我失望了,所以我想我会问这里。假设我有以下C ++代码:

class BaseClass {
public:
  BaseClass() { 
    this->Initialize(); 
  }

  int foo() {
    return this->foo_;
  }

protected:
  virtual int GetInitialValue() { 
    return 1; 
  }

private:
  void Initialize() {
    this->foo_ = this->GetInitialValue();
  }

  int foo_;
};

class DerivedClass : public BaseClass {
public:
  DerivedClass() : BaseClass() {
  }

protected:
  virtual int GetInitialValue() { 
    return 2; 
  }
};

DerivedClass::foo()的返回值应该是多少? BaseClass::GetInitialValue()会被调用,还是永远被DerivedClass::GetInitialValue()调用?我应该在哪里搜索,我应该使用哪些搜索字词来找到答案?

5 个答案:

答案 0 :(得分:4)

GetInitialValue()不是虚拟的,所以无论如何都没有动态调度。

在您的情况下,foo()的预期值为1,因为在_foo构造函数中设置了BaseClass的值。派生类的版本不会被调用,因为首先调用BaseClass构造函数,而DerivedClass成员尚未初始化。

这也是Effective C++中的第9项,该摘要位于artima的此页面中:

http://www.artima.com/cppsource/nevercall.html

该文章的摘要是:

  
    

要记住的事情

         

不要在构造或销毁期间调用虚函数,因为这样的调用永远不会转到比当前正在执行的构造函数或析构函数更多的派生类

  
#include <iostream>

class Base {
   public:
   Base() { this->initialize(); }
   void initialize() { this->_foo = this->getInitial(); }
   virtual int getInitial() { return 1; }

   int _foo;
};

class Derived : public Base {
   public:
      Derived() : Base() {}
      virtual int getInitial() { return 2;}
};


int main()
{
   Base* dp = new Derived();
   std::cout << dp->_foo << std::endl;
}

此代码输出1

答案 1 :(得分:2)

我刚注意到您的代码存在问题。你是从构造函数中间接调用virtual函数的,这是一个非常糟糕的坏主意。

阅读Scott Meyers的这篇文章:

Never Call Virtual Functions during Construction or Destruction

另请阅读此常见问题:

When my base class's constructor calls a virtual function on its this object, why doesn't my derived class's override of that virtual function get invoked?

答案 2 :(得分:2)

在构造函数和析构函数的执行期间,动态类型是构造/销毁类型,因此在这种情况下,调度将不是派生版本。在像

这样的东西
struct Base {
   virtual void f() { std::cout << "Base::f()\n"; }
};

struct D1: Base {
   D1() { f(); }
   virtual void f() { std::cout << "D1::f()\n"; }
};

struct D2: D1 {
   D2() { name = "D2";
   virtual void f() { std::cout << name << "::f()\n"; }
   std::string name;
};

D2类型对象的构造显示“D1 :: f()”,因为在构造D1基础期间调用f()。

要理解原因,请考虑如果调用D2 :: f()会发生什么,名称成员尚未构建...

答案 3 :(得分:0)

这个过程是这样的:

1)构造函数或Derives被称为

2)它调用基类构造函数

3)基类构造函数设置ist vtable

4)执行基类构造函数体(在此构造函数中,使用基类vtable)

5)derives构造函数将其设置为vtable

6)在此处调用构造函数(在此构造函数中,使用的是派生的vtable)

Google:c ++虚拟方法构造函数

例如:http://www.artima.com/cppsource/nevercall.html

否则,您可以搜索C ++标准草案(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/#mailing2011-02)12.7 4

“当从构造函数[...]或析构函数调用虚函数时,调用所适用的对象是正在构造或销毁的对象,在构造函数中定义的函数中调用的函数或destrutor自己的类或它的一个基础,但不是一个函数覆盖派生类“

答案 4 :(得分:0)

我知道我应该自己回答这个问题,但我不能做得比这更好:

在施工或销毁期间永远不要调用虚拟功能 http://www.artima.com/cppsource/nevercall.html

引用那篇文章:

在基类构造期间,虚函数永远不会进入派生类。相反,该对象的行为就像它是基本类型一样。非正式地说,在基类构造期间,虚函数不是。