C++ 在构造函数中调用虚函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/962132/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Calling virtual functions inside constructors
提问by David Coufal
Suppose I have two C++ classes:
假设我有两个 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; }
};
If I write the following code:
如果我编写以下代码:
int main()
{
B b;
int n = b.getn();
}
One might expect that n
is set to 2.
人们可能会期望将n
其设置为 2。
It turns out that n
is set to 1. Why?
原来n
设置为1。为什么?
采纳答案by JaredPar
Calling virtual functions from a constructor or destructor is dangerous and should be avoided whenever possible. All C++ implementations should call the version of the function defined at the level of the hierarchy in the current constructor and no further.
从构造函数或析构函数调用虚函数是危险的,应尽可能避免。所有 C++ 实现都应该调用在当前构造函数的层次结构级别定义的函数版本,而不是进一步调用。
The C++ FAQ Litecovers this in section 23.7 in pretty good detail. I suggest reading that (and the rest of the FAQ) for a followup.
在C ++ FAQ精简版包括这在相当不错的细节部分23.7。我建议阅读(以及其余的常见问题解答)以进行跟进。
Excerpt:
摘抄:
[...] In a constructor, the virtual call mechanism is disabled because overriding from derived classes hasn't yet happened. Objects are constructed from the base up, “base before derived”.
[...]
Destruction is done “derived class before base class”, so virtual functions behave as in constructors: Only the local definitions are used – and no calls are made to overriding functions to avoid touching the (now destroyed) derived class part of the object.
[...] 在构造函数中,虚拟调用机制被禁用,因为尚未发生从派生类的覆盖。对象是从基础向上构造的,“派生前的基础”。
[...]
销毁是在“基类之前的派生类”完成的,因此虚函数的行为就像在构造函数中一样:仅使用本地定义——并且不调用覆盖函数以避免触及对象的(现已销毁的)派生类部分。
EDITCorrected Most to All (thanks litb)
EDIT更正大部分(感谢 litb)
回答by David Rodríguez - dribeas
Calling a polymorphic function from a constructor is a recipe for disaster in most OO languages. Different languages will perform differently when this situation is encountered.
在大多数面向对象语言中,从构造函数调用多态函数是灾难的秘诀。遇到这种情况时,不同的语言会有不同的表现。
The basic problem is that in all languages the Base type(s) must be constructed previous to the Derived type. Now, the problem is what does it mean to call a polymorphic method from the constructor. What do you expect it to behave like? There are two approaches: call the method at the Base level (C++ style) or call the polymorphic method on an unconstructed object at the bottom of the hierarchy (Java way).
基本问题是,在所有语言中,必须在派生类型之前构造基本类型。现在,问题是从构造函数调用多态方法是什么意思。你期望它的行为是什么样的?有两种方法:在 Base 级别调用方法(C++ 样式)或在层次结构底部的未构造对象上调用多态方法(Java 方式)。
In C++ the Base class will build its version of the virtual method table prior to entering its own construction. At this point a call to the virtual method will end up calling the Base version of the method or producing a pure virtual method calledin case it has no implementation at that level of the hierarchy. After the Base has been fully constructed, the compiler will start building the Derived class, and it will override the method pointers to point to the implementations in the next level of the hierarchy.
在 C++ 中,Base 类将在进入自己的构造之前构建其版本的虚拟方法表。此时,对虚方法的调用将最终调用该方法的 Base 版本,或者生成一个纯虚方法,以防它在层次结构的该级别没有实现。在 Base 完全构建之后,编译器将开始构建 Derived 类,它将覆盖方法指针以指向层次结构的下一级中的实现。
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
In Java, the compiler will build the virtual table equivalent at the very first step of construction, prior to entering the Base constructor or Derived constructor. The implications are different (and to my likings more dangerous). If the base class constructor calls a method that is overriden in the derived class the call will actually be handled at the derived level calling a method on an unconstructed object, yielding unexpected results. All attributes of the derived class that are initialized inside the constructor block are yet uninitialized, including 'final' attributes. Elements that have a default value defined at the class level will have that value.
在 Java 中,在进入 Base 构造函数或派生构造函数之前,编译器将在构造的第一步构建等效的虚拟表。影响是不同的(而且在我看来更危险)。如果基类构造函数调用在派生类中被覆盖的方法,该调用实际上将在派生级别处理,调用未构造对象上的方法,从而产生意外结果。在构造函数块内初始化的派生类的所有属性都尚未初始化,包括“final”属性。具有在类级别定义的默认值的元素将具有该值。
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
As you see, calling a polymorphic (virtualin C++ terminology) methods is a common source of errors. In C++, at least you have the guarantee that it will never call a method on a yet unconstructed object...
如您所见,调用多态(C++ 术语中的虚拟)方法是常见的错误来源。在 C++ 中,至少你可以保证它永远不会在尚未构造的对象上调用方法......
回答by David Coufal
The reason is that C++ objects are constructed like onions, from the inside out. Base classes are constructed before derived classes. So, before a B can be made, an A must be made. When A's constructor is called, it's not a B yet, so the virtual function table still has the entry for A's copy of fn().
原因是 C++ 对象像洋葱一样,由内而外构造。基类在派生类之前构造。因此,在制作 B 之前,必须制作 A。当 A 的构造函数被调用时,它还不是 B,所以虚函数表仍然有 A 的 fn() 副本的条目。
回答by Aaron Maenpaa
The C++ FAQ LiteCovers this pretty well:
在C ++ FAQ精简版封面这还算不错:
Essentially, during the call to the base classes constructor, the object is not yet of the derived type and thus the base type's implementation of the virtual function is called and not the derived type's.
本质上,在调用基类构造函数期间,对象还不是派生类型,因此调用的是基类型的虚函数实现,而不是派生类型的。
回答by Tobias
One solution to your problem is using factory methods to create your object.
您的问题的一种解决方案是使用工厂方法来创建您的对象。
- Define a common base class for your class hierarchy containing a virtual method afterConstruction():
- 为包含虚方法 afterConstruction() 的类层次结构定义一个公共基类:
class Object { public: virtual void afterConstruction() {} // ... };
- Define a factory method:
- 定义工厂方法:
template< class C > C* factoryNew() { C* pObject = new C(); pObject->afterConstruction(); return pObject; }
- Use it like this:
- 像这样使用它:
class MyClass : public Object { public: virtual void afterConstruction() { // do something. } // ... }; MyClass* pMyObject = factoryNew();
回答by Fran?ois Andrieux
Other answers have already explained why virtual
function calls don't work as expected when called from a constructor. I'd like to instead propose another possible work around for getting polymorphic-like behavior from a base type's constructor.
其他答案已经解释了为什么virtual
从构造函数调用时函数调用不能按预期工作。我想提出另一种可能的解决方法,用于从基类型的构造函数中获取类似多态的行为。
By adding a template constructor to the base type such that the template argument is always deduced to be the derived type it's possible to be aware of the derived type's concrete type. From there, you can call static
member functions for that derived type.
通过向基类型添加模板构造函数,这样模板参数总是被推导出为派生类型,可以知道派生类型的具体类型。从那里,您可以调用static
该派生类型的成员函数。
This solution does not allow non-static
member functions to be called. While execution is in the base type's constructor, the derived type's constructor hasn't even had time to go through it's member initialization list. The derived type portion of the instance being created hasn't begun being initialized it. And since non-static
member functions almost certainly interact with data members it would be unusual to wantto call the derived type's non-static
member functions from the base type's constructor.
该解决方案不允许static
调用非成员函数。当执行在基类型的构造函数中时,派生类型的构造函数甚至没有时间通过它的成员初始化列表。正在创建的实例的派生类型部分尚未开始对其进行初始化。而且由于非static
成员函数几乎肯定会与数据成员交互,因此想要static
从基类型的构造函数调用派生类型的非成员函数是不寻常的。
Here is a sample implementation :
这是一个示例实现:
#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;
}
This example should print
这个例子应该打印
Derived created
Derived destroyed
Base created
Base destroyed
When a Derived
is constructed, the Base
constructor's behavior depends on the actual dynamic type of the object being constructed.
当一个Derived
构造,该Base
构造函数的行为取决于对象的实际动态类型正在建设中。
回答by stands2reason
As has been pointed out, the objects are created base-down upon construction. When the base object is being constructed, the derived object does not exist yet, so a virtual function override cannot work.
正如已经指出的那样,对象是在构造时创建的。在构造基础对象时,派生对象尚不存在,因此无法进行虚函数覆盖。
However, this can be solved with polymorphic getters that use static polymorphisminstead of virtual functions if your getters return constants, or otherwise can be expressed in a static member function, This example uses CRTP (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern).
但是,这可以通过使用静态多态而不是虚函数的多态 getter 来解决,如果您的 getter 返回常量,或者可以在静态成员函数中表示,此示例使用 CRTP ( https://en.wikipedia.org/wiki /Curiously_recurring_template_pattern)。
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;
}
With the use of static polymorphism, the base class knows which class' getter to call as the information is provided at compile-time.
通过使用静态多态,基类知道在编译时提供信息时调用哪个类的 getter。
回答by msc
The C++ Standard (ISO/IEC 14882-2014)say's:
在C ++标准(ISO / IEC 14882-2014)说的:
Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class's non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor's or destructor's class and not one overriding it in a more-derived class. If the virtual function call uses an explicit class member access (5.2.5) and the object expression refers to the complete object of x or one of that object's base class subobjects but not x or one of its base class subobjects, the behavior is undefined.
成员函数,包括虚函数 (10.3),可以在构造或销毁 (12.6.2) 期间调用。当从构造函数或析构函数直接或间接调用虚函数时,包括在类的非静态数据成员的构造或析构期间,并且该调用适用的对象是正在构造的对象(称为 x)或析构函数,被调用的函数是构造函数或析构函数类中的最终覆盖程序,而不是在派生程度更高的类中覆盖它。如果虚函数调用使用显式类成员访问(5.2.5)并且对象表达式引用 x 的完整对象或该对象的基类子对象之一,但不是 x 或其基类子对象之一,则行为未定义.
So, Don't invoke virtual
functions from constructors or destructors that attempts to call into the object under construction or destruction, Because the order of construction starts from base to derivedand the order of destructors starts from derived to base class.
所以,不要virtual
从构造函数或析构函数调用函数,这些函数试图调用正在构造或销毁的对象,因为构造的顺序是从基类到派生的,而析构函数的顺序是从派生到基类的。
So, attempting to call a derived class function from a base class under construction is dangerous.Similarly, an object is destroyed in reverse order from construction, so attempting to call a function in a more derived class from a destructor may access resources that have already been released.
因此,尝试从正在构造的基类中调用派生类函数是危险的。同样,对象以与构造相反的顺序被销毁,因此尝试从析构函数调用派生类中的函数可能会访问已经存在的资源被释放。
回答by Yogesh
The vtables are created by the compiler. A class object has a pointer to its vtable. When it starts life, that vtable pointer points to the vtable of the base class. At the end of the constructor code, the compiler generates code to re-point the vtable pointer to the actual vtable for the class. This ensures that constructor code that calls virtual functions calls the base class implementations of those functions, not the override in the class.
vtable 是由编译器创建的。一个类对象有一个指向它的 vtable 的指针。当它开始运行时,那个 vtable 指针指向基类的 vtable。在构造函数代码的末尾,编译器生成代码以将 vtable 指针重新指向该类的实际 vtable。这确保调用虚函数的构造函数代码调用这些函数的基类实现,而不是类中的覆盖。
回答by TimW
Do you know the crash error from Windows explorer?! "Pure virtual function call ..."
Same problem ...
您知道 Windows 资源管理器的崩溃错误吗?!“纯虚函数调用……”
同样的问题……
class AbstractClass
{
public:
AbstractClass( ){
//if you call pureVitualFunction I will crash...
}
virtual void pureVitualFunction() = 0;
};
Because there is no implemetation for the function pureVitualFunction() and the function is called in the constructor the program will crash.
因为函数 pureVitualFunction() 没有实现,并且在构造函数中调用了该函数,所以程序会崩溃。