Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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. Obviously, class A's constructor function calls itself.
  2. "static A a" will get stuck in a loop.
  3. On VS2013, this code can get out from the loop and print "End" on the console.
  4. On VS2017, this code gets stuck in a loop.

    **What does VS2013 do for this code?

like image 730
MiC Avatar asked Jan 14 '19 07:01

MiC


2 Answers

Nothing in particular has to happen. According to the C++ standard:

[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 ]

The statement I emboldened is exactly what happens in your program. It's also what the standard's example shows as undefined. The language specification says an implementation can do whatever it deems appropriate. So it could cause an infinite loop, or it may not, depending on the synchronization primitives your implementation uses to prevent concurrent reentry into the block (the initialization has to be thread safe).

Even before C++11, the behavior of recursive reentry was undefined. An implementation could do anything to make sure an object is initialized only once, which in turn could produce different results.

But you can't expect anything specific to happen portably. Not to mention undefined behavior always leaves room for a small chance of nasal demons.

like image 195
StoryTeller - Unslander Monica Avatar answered Oct 31 '22 02:10

StoryTeller - Unslander Monica


The behaviour is undefined. The reason it "worked" in Visual Studio 2013 is that it didn't implement thread safe initialisation of function statics. What is probably happening is that the first call to GetA() creates a and calls the constructor. The second call to GetA() then just returns the partially constructed a. As the body of your constructor doesn't initialise anything calling Print() doesn't crash.

Visual Studio 2017 does implement thread safe initialisation. and presumably locks some mutex on entry to GetA() if a is not initialised, the second call to GetA() then encounters the locked mutex and deadlocks.

Note in both cases this is just my guess from the observed behaviour, the actual behaviour is undefined, for example GetA() may end up creating 2 instances of A.

like image 29
Alan Birtles Avatar answered Oct 31 '22 00:10

Alan Birtles