Что происходит, когда функция конструктора вызывает себя в VS2013? - PullRequest
0 голосов
/ 14 января 2019
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. Очевидно, что функция конструктора класса А. вызывает сама себя.
  2. «статический А» застрянет в петле.
  3. На VS2013 этот код может выйти из цикла и напечатать «End» на консоли.
  4. На VS2017 этот код застревает в цикле.

    ** Что VS2013 делает для этого кода?

Ответы [ 2 ]

0 голосов
/ 14 января 2019

Поведение не определено. Причина, по которой он «работал» в Visual Studio 2013, заключается в том, что в нем не реализована поточно-ориентированная инициализация статических функций. Вероятно, происходит то, что первый вызов GetA() создает a и вызывает конструктор. Второй вызов GetA() затем просто возвращает частично построенный a. Поскольку тело вашего конструктора не инициализирует ничего, вызов Print() не приводит к сбою.

В Visual Studio 2017 реализована поточно-ориентированная инициализация. и предположительно блокирует некоторый мьютекс при входе в GetA(), если a не инициализирован, то второй вызов GetA() затем обнаруживает заблокированный мьютекс и взаимоблокировки.

Обратите внимание, что в обоих случаях это только мое предположение из наблюдаемого поведения, фактическое поведение не определено, например, GetA() может в итоге создать 2 экземпляра A.

0 голосов
/ 14 января 2019

Ничего особенного не должно произойти. Согласно стандарту C ++:

[stmt.dcl] (выделено мое)

4 Динамическая инициализация переменной области блока со статическим хранилищем продолжительность или продолжительность хранения потока выполняется в первый раз контроль проходит через его декларацию; такая переменная считается инициализируется после завершения его инициализации. Если инициализация завершается с помощью исключения, инициализация не завершено, поэтому он будет повторен при следующем входе управления декларация. Если контроль вводит декларацию одновременно переменная инициализируется, параллельное выполнение должно ждать для завершения инициализации. Если контроль снова входит в объявление рекурсивно во время инициализации переменной, поведение не определено. [ Пример :

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

- конец примера ]

Утверждение, которое я ободрил, именно то, что происходит в вашей программе. Это также то, что пример стандарта показывает как неопределенный. Спецификация языка говорит, что реализация может делать все, что сочтет целесообразным. Таким образом, это может вызвать бесконечный цикл, а может и нет, в зависимости от примитивов синхронизации, которые ваша реализация использует для предотвращения одновременного повторного входа в блок (инициализация должна быть поточно-безопасной).

Еще до C ++ 11 поведение рекурсивного повторного входа было неопределенным. Реализация может сделать что угодно, чтобы убедиться, что объект инициализируется только один раз, что, в свою очередь, может привести к различным результатам.

Но вы не можете ожидать, что что-то конкретное случится переносимо. Не говоря уже о неопределенном поведении, всегда остается место для небольшого шанса носовых демонов.

...