在构造函数内调用虚函数 假设我有两个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()的类层次结构定义公共基类:
类对象 { 公众: 虚空后构造(){} // ... };
  • 定义工厂方法:
模板 C*factoryNew() { C*pObject=newc(); pObject->afterConstruction(); 返回对象; }
  • 像这样使用它:
类MyClass:公共对象 { 公众: 虚空后构造() { //做点什么。 } // ... }; MyClass*pMyObject=factoryNew();
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