这是正确的:在构造Base对象之前调用Derived的虚方法吗?

时间:2012-07-04 21:16:07

标签: c++ constructor g++ standards virtual-method

我知道在Base类的构造函数中 - 当调用虚方法时 - 调用Base方法而不是派生 - 请参阅Calling virtual functions inside constructors

我的问题与此主题有关。我只是想知道如果我在Derived类构造函数中调用虚方法会发生什么 - 但是在构造Base部分之前。我的意思是调用虚方法来评估Base类的构造函数参数,参见代码:

class Base {
public:
  Base(const char* name) : name(name) {
    cout << "Base():" << name << endl;
  }
  virtual const char* getName() { 
    cout << "Base::getName()" << endl;
    return "Base";
  }
protected:
  const char* name;
};

class Derived : public Base {
public:
  Derived() : Base(getName()) {
    cout << "Derived():" << name << endl;
  }
  virtual const char* getName() { 
    cout << "Derived::getName()" << endl;
    return "Derived";
  }
};

int main() {
  Derived d;
}

编译器g ++(4.3.x-4.5x版本)输出为:

Derived::getName()
Base():Derived
Derived():Derived 

但是我希望:

Base::getName()
Base():Base
Derived():Base

这看起来不错 - 但请考虑这个产生segmentation fault

的例子
class Derived : public Base {
public:
  Derived() : Base(getName()), name(new string("Derived")) {
    cout << "Derived():" << Base::name << endl;
  }
  virtual const char* getName() { 
    cout << "Derived::getName()" << endl;
    return name->c_str();
  }
private:
  string* name;
};

请回答:这是正确的g ++行为吗? C ++标准对此有何看法?也许这是未定义的行为?

[UPDATE1] 我考虑到罗伯特和奥利的答案 - 我改变了我的第一个例子。然后getName()被称为“虚拟” - 它会产生分段错误。请回答我对这部分的问题。

const char* virtualGetName(Base* basePtr)
{
  return basePtr->getName();
}

class Derived : public Base {
public:
  Derived() : Base(virtualGetName(this)) {
    cout << "Derived():" << Base::name << endl;
  }
  virtual const char* getName() { 
    cout << "Derived::getName()" << endl;
    return "Derived";
  }
};

2 个答案:

答案 0 :(得分:11)

您的所有示例都显示未定义的行为。 C ++语言标准(C ++11§12.6.2/ 13):

  

可以为正在构建的对象调用成员函数(包括虚拟成员函数)。同样,正在构建的对象可以是typeid运算符或dynamic_cast的操作数。

     

但是,如果在 ctor-initializer (或直接或间接从 ctor-initializer 中调用的函数)中执行这些操作,那么基类的mem-initializers 已经完成,操作的结果是未定义的。

您正在从getName()类构造函数的初始化列表( ctor-initializer )中调用成员函数Derived。此成员函数调用必须在Base的初始化程序完成之前发生( mem-initializer 表示Base),因为它是初始化程序本身的参数。

因此,行为未定义。

通常,Never Call Virtual Functions during Construction or Destruction

答案 1 :(得分:1)

  

我只是想知道如果我在Derived类构造函数中调用虚方法会发生什么 - 但是在构建Base部分之前会发生什么。

看起来你可能正在这样做,但你不是。

在第一个示例中,Derived::getName()不依赖this,因此方法调用有效。在第二个示例中,Derived::getName()确实取决于this。由于尚未设置this->name,因此它指向未定义的位置并为您提供段错误。

this->name尚未设置,因为Derived构造函数所做的第一件事就是调用Base构造函数。如果指定要传递给Base构造函数的参数,则首先处理它们。然后它通过调用它们的构造函数来实例化类的成员变量。初始化列表可用于将参数传递给那些构造函数,但它不能更改它们被调用的顺序。此步骤是name初始化的位置。最后,执行Derived构造函数的主体。