假设我有两个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; }
};

如果我写下面的代码:

int main()
{
  B b;
  int n = b.getn();
}

有人可能认为n被设为2。

结果是n被设为1。为什么?


当前回答

作为补充,调用尚未完成构造的对象的虚函数也将面临同样的问题。

例如,在对象的构造函数中启动一个新线程,并将该对象传递给新线程,如果新线程在对象完成构造之前调用该对象的虚函数将导致意想不到的结果。

例如:

#include <thread>
#include <string>
#include <iostream>
#include <chrono>

class Base
{
public:
  Base()
  {
    std::thread worker([this] {
      // This will print "Base" rather than "Sub".
      this->Print();
    });
    worker.detach();
    // Try comment out this code to see different output.
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  virtual void Print()
  {
    std::cout << "Base" << std::endl;
  }
};

class Sub : public Base
{
public:
  void Print() override
  {
    std::cout << "Sub" << std::endl;
  }
};

int main()
{
  Sub sub;
  sub.Print();
  getchar();
  return 0;
}

这将输出:

Base
Sub

其他回答

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.

因此,不要从构造函数或析构函数中调用试图调用正在构造或销毁的对象的虚函数,因为构造函数的顺序从基类开始到派生类,而析构函数的顺序从派生类开始到基类。

因此,试图从正在构建的基类调用派生类函数是危险的。类似地,对象以与构造相反的顺序被销毁,因此试图从析构函数调用派生类中的函数可能会访问已经释放的资源。

我看不出这里虚拟关键词的重要性。B是一个静态类型变量,它的类型由编译器在编译时确定。函数调用不会引用虚表。当b被构造时,它的父类的构造函数被调用,这就是为什么_n的值被设置为1。

你知道Windows资源管理器的崩溃错误吗?“纯虚函数调用…” 同样的问题…

class AbstractClass 
{
public:
    AbstractClass( ){
        //if you call pureVitualFunction I will crash...
    }
    virtual void pureVitualFunction() = 0;
};

由于pureVitualFunction()函数没有实现,并且在构造函数中调用该函数,因此程序将崩溃。

虚表是由编译器创建的。 类对象有一个指向虚表的指针。当它开始生命时,虚表指针指向虚表 基类的。在构造函数代码的末尾,编译器生成重新指向虚表指针的代码 到类的实际虚函数表。这样可以确保调用虚函数的构造函数代码调用 这些函数的基类实现,而不是类中的重写。

解决这个问题的一个方法是使用工厂方法来创建对象。

为你的类层次结构定义一个公共基类,其中包含一个虚拟方法afterConstruction():

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();