当构造函数在 VS2013 中调用自身时会发生什么?

What happens when a constructor function calls itself in VS2013?

class IA
{
   public:
   virtual void Print() = 0;
}
IA* GetA();

class A : public IA
{
   public:
   int num = 10;
   A()
   {
     GetA()->Print();
   }
   void Print()
   {
     std::cout << num << std::endl;
   }
}

IA* GetA()
{
   static A a;
   return &a;
}

int main()
{
   GetA();
   std::cout << "End" << std::endl;
   getchar();
   return 0; 
}
  1. 很明显,classA的构造函数调用了自己。
  2. "static A a" 会陷入循环。
  3. 在VS2013上,这段代码可以跳出循环并在控制台打印"End"。
  4. 在 VS2017 上,此代码陷入循环。

    **这段代码VS2013做了什么?

没有什么特别需要发生的。根据 C++ 标准:

[stmt.dcl] (emphasis mine)

4 Dynamic initialization of a block-scope variable with static storage duration or thread storage duration is performed the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization. If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined. [ Example:

int foo(int i) {
  static int s = foo(2*i);      // recursive call - undefined
  return i+1;
}

 — end example ]

我鼓励的说法正是您程序中发生的情况。这也是标准示例显示为未定义的内容。语言规范说一个实现可以做任何它认为合适的事情。因此它可能会导致无限循环,也可能不会,这取决于您的实现用于防止并发重新进入块的同步原语(初始化必须是线程安全的)。

甚至在 C++11 之前,递归重入的行为是未定义的。一个实现可以做任何事情来确保一个对象只被初始化一次,这反过来可能会产生不同的结果。

但是您不能指望任何特定的事情都可以移植。更何况未定义的行为总是给鼻魔留有余地

行为未定义。它在 Visual Studio 2013 中 "worked" 的原因是它没有实现函数静态的线程安全初始化。可能发生的情况是,第一次调用 GetA() 会创建 a 并调用构造函数。第二次调用 GetA() 然后只是 returns 部分构造的 a。由于构造函数的主体没有初始化任何调用 Print() 不会崩溃。

Visual Studio 2017 确实实现了线程安全初始化。如果 a 未初始化,则可能会在进入 GetA() 时锁定一些互斥量,然后对 GetA() 的第二次调用会遇到锁定的互斥量和死锁。

请注意,在这两种情况下,这只是我根据观察到的行为做出的猜测,实际行为是未定义的,例如 GetA() 可能最终会创建 A.

的 2 个实例