C++ - Порядок уничтожения. Статические члены функции уничтожаются до деструктора основного класса.

Я создаю проект С++ с помощью компилятора Embarcadero RAD Studio XE7. В этом проекте у меня есть следующий дизайн кода:

  • Основная форма, унаследованная от TForm, содержащая деструктор
  • Класс "фу"
  • Класс "bar", в котором класс "foo" является статическим членом.

Теперь из деструктора основной формы мне нужно выполнить функцию, содержащуюся в классе foo. Итак, в моем деструкторе основной формы я разместил такой код:

__fastcall TForm1::~TForm1()
{
    Bar::m_Foo.ExecuteSomething();
}

Однако в этот момент мое приложение вылетает, в моем случае с ошибкой «Вызывается чистая виртуальная функция» (эта ошибка, конечно, зависит от моей реализации, я не буду вдаваться в подробности здесь). Дело в том, что мой класс Bar::m_Foo был удален до деструктора TForm1.

Чтобы обозначить проблему, я воссоздал здесь минимальный пример кода:

Основной.ч

#ifndef MainH
#define MainH

#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>

class TForm1 : public TForm
{
    __published:

    public:
        __fastcall TForm1(TComponent* Owner);
        virtual __fastcall ~TForm1();

    private:
};
extern PACKAGE TForm1 *Form1;
#endif

Main.cpp

#include <vcl.h>
#pragma hdrstop
#include "Main.h"

#include <iostream.h>

#pragma package(smart_init)
#pragma resource "*.dfm"

//---------------------------------------------------------------------------
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    std::cout << "TForm1 constructor - CALLED" << std::endl;
}
//---------------------------------------------------------------------------
__fastcall TForm1::~TForm1()
{
    std::cout << "TForm1 destructor - CALLED" << std::endl;
}
//---------------------------------------------------------------------------

Класс.h

#ifndef AClassH
#define AClassH

#include <Windows.h>

class Foo
{
    public:
        Foo()
        {
            std::cout << "Foo constructor - CALLED" << std::endl;
        }

        virtual ~Foo()
        {
            std::cout << "Foo destructor - CALLED" << std::endl;
        }
};

class Bar
{
    private:
        static Foo m_Foo;
};

#endif

Класс.cpp

#include "Class.h"

//---------------------------------------------------------------------------
Foo Bar::m_Foo;
//---------------------------------------------------------------------------

После выполнения приведенный выше код показывает следующий результат:

Foo constructor - CALLED
TForm1 constructor - CALLED
Foo destructor - CALLED
TForm1 destructor - CALLED

Это подчеркивает, что деструктор статического члена вызывается ДО основного деструктора формы, что делает любое использование класса Foo опасным в моем деструкторе TForm1. Этот результат заставил меня немного озадачиться, потому что я всегда верил, что статическая переменная-член выходит из области видимости в то же время, когда приложение закрывается, то есть ПОСЛЕ вызова деструктора моей основной формы. Но похоже, что это не так.

Итак, мои вопросы:

  • Каковы правила для таких статических членов и когда они выходят за рамки?
  • Почему мой деструктор Foo вызывается перед деструктором основной формы?
  • Определен ли этот порядок уничтожения в стандартах С++ или это ошибка RAD Studio?
  • В моем случае функция, вызываемая деструктором формы, используется для освобождения глобального экземпляра GDI+. Поскольку я использую GDI+ в общем контексте (основной exe ПЛЮС dll), вызов основной формы может снять окончательную блокировку или нет. По этой причине ключевое слово static здесь важно. Но я делаю что-то не так? Какой дизайн может быть лучше?

------------------------- РЕДАКТИРОВАТЬ ------------------------ ---------

Вот также код основной точки входа приложения из приведенного выше примера, где создается и удаляется TForm1:

#include <vcl.h>
#pragma hdrstop
#include <tchar.h>
//---------------------------------------------------------------------------
USEFORM("Main.cpp", Form1);
//---------------------------------------------------------------------------
int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    try
    {
         Application->Initialize();
         Application->MainFormOnTaskBar = true;
         Application->CreateForm(__classid(TForm1), &Form1);
         Application->Run();
    }
    catch (Exception &exception)
    {
         Application->ShowException(&exception);
    }
    catch (...)
    {
         try
         {
             throw Exception("");
         }
         catch (Exception &exception)
         {
             Application->ShowException(&exception);
         }
    }
    return 0;
}
//---------------------------------------------------------------------------

person Jean-Milost Reymond    schedule 18.11.2017    source источник
comment
Где/как назначается Form1? Как создается и уничтожается TForm1?   -  person Alan Stokes    schedule 18.11.2017
comment
Хорошее замечание. Опубликовано, спасибо   -  person Jean-Milost Reymond    schedule 18.11.2017


Ответы (1)


Наконец-то я нашел причину, по которой мой деструктор статического члена вызывается перед деструктором основной формы. Это связано со свойством, характерным для компиляторов Embarcadero RAD Studio.

На самом деле компилятор генерирует некоторый автоматический код при создании нового проекта. В этом коде (и как это видно из приведенных выше примеров) основная форма создается и хранится внутри другого объекта с именем Application, который является... статическим глобальным объектом.

Вот как объект Application объявляется в RAD Studio VCL:

...

{ Global objects }

var
  Application: TApplication;

...

Конечно, в этих условиях основной объект формы становится зависимым от уничтожения своего родителя, и, поскольку это статический объект, он может быть уничтожен случайным образом, в то время как все статические объекты выходят за пределы области действия, пока приложение завершает работу.

И это также объясняет, почему вызов статической функции-члена из деструктора основной формы является ошибкой, что в моем случае вызвало такое странное нарушение прав доступа.

person Jean-Milost Reymond    schedule 24.11.2017