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

What happens when a constructor function calls itself in VS2013?

本文关键字:什么 构造函数 VS2013 调用      更新时间:2023-10-16
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. 显然,类 A 的构造函数调用自身。
  2. "静态 A a"将卡在循环中。
  3. 在VS2013上,此代码可以从循环中出来并在控制台上打印"End"。
  4. 在VS2017上,此代码陷入循环。

    **VS2013对此代码有什么作用?

没有什么特别的事情必须发生。根据C++标准:

[stmt.dcl](强调我的)

4 使用静态存储动态初始化块范围变量 持续时间或线程存储持续时间在第一次执行 控制通过其声明;考虑这样的变量 初始化完成后初始化。如果 初始化通过抛出异常退出,初始化为 不完整,因此下次控件进入时将再次尝试 宣言。如果控件同时输入声明,而 变量正在初始化,并发执行应等待 以完成初始化。如果控件重新进入 在初始化变量时递归声明, 行为未定义。[示例

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

结束示例]

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

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

但是你不能指望任何特定的事情会随身携带。更不用说未定义的行为总是为鼻魔的小机会留下空间。

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

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

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