也许这在其他一些话题中有所涉及。但我没有找到任何令人满意的答案。有人可以跟我解释一下吗?我有以下代码:
#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类中被覆盖了?
答案 0 :(得分:2)
在构造函数中,正在构造的对象的类是构造函数所属的类的类。在这种情况下,在Base
构造函数中,正在构造的对象的类是Base
。你不能从构造函数内部进行虚拟调用,它们都是静态解析的。
在构造函数中允许虚拟调用会非常危险。在构造函数中,执行顺序为:
当然,这是递归的:每个基类构造函数中的执行顺序是相同的。
现在,想象如果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类)。显然,这太危险了。在这一点上,似乎逻辑上调用函数,它可以使用创建的数据(在这种情况下,它是基类的数据)