在创建者中调用的方法是从基类调用的,而不是从派生类调用的

时间:2016-09-08 07:45:27

标签: c++

也许这在其他一些话题中有所涉及。但我没有找到任何令人满意的答案。有人可以跟我解释一下吗?我有以下代码:

#include <iostream>

class Base {
public:
   Base() {
      foo();
   }

   virtual void foo() { 
      std::cout << "Base::foo()" << std::endl; 
   }
};

class Derived : public Base {
public:
   Derived(): Base() {}

   virtual void foo() { 
      std::cout << "Derived::foo()" << std::endl; 
   }
};

int main() {
   Derived* p = new Derived();
}

现在我的问题是为什么Base创建者调用foo方法,它位于Base而不是Derived类中,尽管它在Derived类中被覆盖了?

4 个答案:

答案 0 :(得分:2)

在构造函数中,正在构造的对象的类是构造函数所属的类的类。在这种情况下,在Base构造函数中,正在构造的对象的类是Base。你不能从构造函数内部进行虚拟调用,它们都是静态解析的。

在构造函数中允许虚拟调用会非常危险。在构造函数中,执行顺序为:

  • 基类(es)构造函数
  • 数据成员构造函数,按照在类定义中声明数据成员的顺序
  • 构造函数主体

当然,这是递归的:每个基类构造函数中的执行顺序是相同的。

现在,想象如果Derived::foo()使用Derived中定义的数据成员会发生什么。执行Base构造函数时,尚未构造那些数据成员。在那种情况下会发生什么?当然,可怕的未定义行为。

因此,在构造函数中调用虚函数通常不是一个好主意。如您所见,调用的函数可能不是预期的函数(因为实际上没有虚拟调用);但是,进行虚拟调用可能很容易导致未定义的行为,这总是很糟糕。

答案 1 :(得分:1)

弗罗姆here

  

仅使用 local 定义 - 并且不会对覆盖函数进行调用以避免触及对象的派生类部分。

&#c; ctors 中,仅使用 local 定义(在该类的范围意义上是本地的)。

这样做是为了避免触及可能尚未初始化的对象的派生类部分。

这个例子不言自明:

 class B {
    public:
        B(const string& ss) { cout << "B constructor\n"; f(ss); }
        virtual void f(const string&) { cout << "B::f\n";}
    };

 class D : public B {
    public:
        D(const string & ss) :B(ss) { cout << "D constructor\n";}
        void f(const string& ss) { cout << "D::f\n"; s = ss; }
    private:
        string s;
    };

如果在构造函数D::f中调用B::B,那么它会尝试为未初始化的string s分配值(因为构造函数D::D不会被调用然而)。

这就是为什么 C ++ 标准行为已如此定义。

答案 2 :(得分:1)

C ++中的虚函数调用是根据调用中使用的对象的 dynamic 类型解析的(这是虚函数与非虚函数的区别)。

当类Base的构造函数(或析构函数)处于活动状态时,对象的动态类型始终被视为Base,即使该Base对象是&#34 ;嵌入式&#34;进入一些较大的派生对象(例如Derived)。因此,在Base构造函数处于活动状态时进行的所有虚拟调用都将解析为Base的成员。

请注意,在这种情况下,未正式禁用虚拟呼叫机制。 (声称从构造函数中进行的虚拟调用已被解析并且静态地#34;)是不正确的。虚拟呼叫机制仍然照常工作。只是层次结构树是&#34;上限&#34; /&#34;修剪&#34;在构造函数当前处于活动状态的类中。

此规则背后的基本原理非常自然:Base构造函数处于活动状态时,Derived尚未构建。访问Derived的任何成员都是危险的。此外,试图解决这个限制(这是可能的)通常会导致不确定的行为。相同的逻辑对称地适用于析构函数。

答案 3 :(得分:1)

在构造函数中调用虚函数是不好的做法。我只举一个简单的例子,它将显示这种方法的缺点(注意:它是关于c ++(其他语言可以使用不同的实现)。

代码中调用构造函数的顺序是:

1)基地

2)派生

在调用Base构造函数时,不会创建Derived类中指定的成员。假设,您可以在Derived类中使用覆盖的函数。在这种情况下,您可以调用函数,这些函数可以访问NOT CREATED DATA(注意,目前未创建Derived类)。显然,这太危险了。在这一点上,似乎逻辑上调用函数,它可以使用创建的数据(在这种情况下,它是基类的数据)