在构造函数内调用虚函数 假设我有两个C++类: class A { public: A() { fn(); } virtual void fn() { _n = 1; } int getn() { return _n; } protected: int _n; }; class B : public A { public: B() : A() {} virtual void fn() { _n = 2; } };
如果我编写以下代码:在构造函数内调用虚函数 假设我有两个C++类: class A { public: A() { fn(); } virtual void fn() { _n = 1; } int getn() { return _n; } protected: int _n; }; class B : public A { public: B() : A() {} virtual void fn() { _n = 2; } };,c++,constructor,overriding,virtual-functions,C++,Constructor,Overriding,Virtual Functions,如果我编写以下代码: int main() { B b; int n = b.getn(); } 人们可能期望n设置为2 结果是n被设置为1。为什么? < p>原因是C++对象是像洋葱一样,从内向外构造的。基类在派生类之前构造。所以,在生成B之前,必须先生成a。调用A的构造函数时,它还不是B,因此虚拟函数表中仍然有A的fn()副本的条目。从构造函数或析构函数调用虚拟函数是危险的,应尽可能避免。所有C++实现都应该调用当前构造函数中层次结构定义的函数的版本,而不必再进一步。 第23.7节
int main()
{
B b;
int n = b.getn();
}
人们可能期望n
设置为2
结果是
n
被设置为1。为什么? < p>原因是C++对象是像洋葱一样,从内向外构造的。基类在派生类之前构造。所以,在生成B之前,必须先生成a。调用A的构造函数时,它还不是B,因此虚拟函数表中仍然有A的fn()副本的条目。从构造函数或析构函数调用虚拟函数是危险的,应尽可能避免。所有C++实现都应该调用当前构造函数中层次结构定义的函数的版本,而不必再进一步。
第23.7节详细介绍了这一点。我建议阅读这篇文章(以及其他常见问题解答)作为后续内容
摘录:
[…]在构造函数中,虚拟调用机制被禁用,因为派生类的重写尚未发生。对象是从“派生之前的基础”开始构造的
[……]
销毁是在“基类之前的派生类”中完成的,因此虚拟函数的行为与构造函数中的一样:只使用本地定义,并且不调用重写函数以避免触及对象的(现在已销毁的)派生类部分
编辑对所有内容进行了最全面的更正(感谢litb)这本书很好地涵盖了这一点:
本质上,在调用基类构造函数的过程中,对象还不是派生类型,因此调用虚函数的基类实现而不是派生类型
你知道Windows资源管理器的崩溃错误吗“纯虚拟函数调用…”
同样的问题
class AbstractClass
{
public:
AbstractClass( ){
//if you call pureVitualFunction I will crash...
}
virtual void pureVitualFunction() = 0;
};
由于函数pureVitualFunction()没有实现,并且该函数在构造函数中被调用,因此程序将崩溃 在大多数OO语言中,从构造函数调用多态函数会导致灾难。遇到这种情况时,不同的语言将执行不同的操作 基本问题是,在所有语言中,基类型必须在派生类型之前构造。现在,问题是从构造函数调用多态方法意味着什么。你认为它会是什么样子?有两种方法:在底层调用方法(C++风格)或在层次结构底部的未构造对象上调用多态方法(Java方式)
C++中,基类将在进入自己的构造之前构建其虚拟方法表的版本。此时,对虚拟方法的调用将最终调用该方法的基本版本,或者生成一个纯虚拟方法,以防它在该层次结构级别上没有实现。基类完全构造完成后,编译器将开始构建派生类,并重写方法指针以指向层次结构下一级的实现
class Base {
public:
Base() { f(); }
virtual void f() { std::cout << "Base" << std::endl; }
};
class Derived : public Base
{
public:
Derived() : Base() {}
virtual void f() { std::cout << "Derived" << std::endl; }
};
int main() {
Derived d;
}
// outputs: "Base" as the vtable still points to Base::f() when Base::Base() is run
正如您所见,调用多态(虚拟C++语言)方法是常见的错误来源。在C++中,至少你可以保证它永远不会调用一个尚未构造的对象的方法…
在对象的构造函数调用中,虚函数指针表没有完全建立。这样做通常不会给你预期的行为。在这种情况下调用虚拟函数可能会起作用,但不能保证,并且应避免携带,并遵循C++标准。p> 解决问题的一个方法是使用工厂方法创建对象- 为包含虚拟方法afterConstruction()的类层次结构定义公共基类:
- 定义工厂方法:
- 像这样使用它:
vtables由编译器创建。 类对象有一个指向其vtable的指针。当它开始使用时,vtable指针指向vtable 基类的。在构造函数代码的末尾,编译器生成代码来重新指向vtable指针 到类的实际vtable。这确保调用虚拟函数的构造函数代码调用
这些函数的基类实现,而不是类中的重写。我没有看到虚拟关键字在这里的重要性。b是一个静态类型变量,其类型由编译器在编译时确定。函数调用不会引用vtable。构造b时,会调用其父类的构造函数,这就是为什么_n的值设置为1 发言权: 可以调用成员函数,包括虚拟函数(10.3) 施工或破坏期间(12.6.2)。当一个虚拟函数 直接或间接地从构造函数或 析构函数,包括在构建或销毁 类的非静态数据成员,以及调用 应用是正在构造或销毁的对象(称为x), 调用的函数是构造函数的or中的最终重写器 析构函数的类,而不是在更派生的类中重写它的类。 如果虚拟函数调用使用显式类成员访问 (5.2.5)且对象表达式指x的完整对象 或该对象的一个基类子对象,但不是x或其子对象之一 基类子对象,行为是未定义的 所以,不要调用
virtual
fun
public class Base {
public Base() { polymorphic(); }
public void polymorphic() {
System.out.println( "Base" );
}
}
public class Derived extends Base
{
final int x;
public Derived( int value ) {
x = value;
polymorphic();
}
public void polymorphic() {
System.out.println( "Derived: " + x );
}
public static void main( String args[] ) {
Derived d = new Derived( 5 );
}
}
// outputs: Derived 0
// Derived 5
// ... so much for final attributes never changing :P
class Object
{
public:
virtual void afterConstruction() {}
// ...
};
template< class C >
C* factoryNew()
{
C* pObject = new C();
pObject->afterConstruction();
return pObject;
}
class MyClass : public Object
{
public:
virtual void afterConstruction()
{
// do something.
}
// ...
};
MyClass* pMyObject = factoryNew();
template<typename DerivedClass>
class Base
{
public:
inline Base() :
foo(DerivedClass::getFoo())
{}
inline int fooSq() {
return foo * foo;
}
const int foo;
};
class A : public Base<A>
{
public:
inline static int getFoo() { return 1; }
};
class B : public Base<B>
{
public:
inline static int getFoo() { return 2; }
};
class C : public Base<C>
{
public:
inline static int getFoo() { return 3; }
};
int main()
{
A a;
B b;
C c;
std::cout << a.fooSq() << ", " << b.fooSq() << ", " << c.fooSq() << std::endl;
return 0;
}
#include <iostream>
#include <string>
struct Base {
protected:
template<class T>
explicit Base(const T*) : class_name(T::Name())
{
std::cout << class_name << " created\n";
}
public:
Base() : class_name(Name())
{
std::cout << class_name << " created\n";
}
virtual ~Base() {
std::cout << class_name << " destroyed\n";
}
static std::string Name() {
return "Base";
}
private:
std::string class_name;
};
struct Derived : public Base
{
Derived() : Base(this) {} // `this` is used to allow Base::Base<T> to deduce T
static std::string Name() {
return "Derived";
}
};
int main(int argc, const char *argv[]) {
Derived{}; // Create and destroy a Derived
Base{}; // Create and destroy a Base
return 0;
}
Derived created
Derived destroyed
Base created
Base destroyed
Base
Sub