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

MiC*_*MiC 15 c++ visual-studio-2013 visual-studio-2017

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; 
}
Run Code Online (Sandbox Code Playgroud)
  1. 显然,A类的构造函数调用自身.
  2. "静态A"将陷入循环中.
  3. 在VS2013上,此代码可以从循环中取出并在控制台上打印"结束".
  4. 在VS2017上,此代码卡在循环中.

    **VS2013为此代码做了什么?

Sto*_*ica 18

没有什么特别必须发生.根据C++标准:

[stmt.dcl](强调我的)

4第一次控制通过其声明时,执行具有静态存储持续时间或线程存储持续时间的块范围变量的动态初始化; 这样的变量在初始化完成后被认为是初始化的.如果通过抛出异常退出初始化,则初始化未完成,因此下次控制进入声明时将再次尝试初始化.如果控制在初始化变量时同时进入声明,则并发执行应等待初始化完成.如果控件在初始化变量时以递归方式重新输入声明,则行为未定义.[  例如:

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

 -  结束例子  ]

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

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

但你不能指望任何特定的东西可以轻松地发生.更不用说未定义的行为总是为鼻子恶魔的小机会留出空间.

  • 并且不要忘记VS应该使用非递归同步原语的选项. (2认同)