C ++ Управление порядком деструкторов для глобальных объектов - PullRequest
4 голосов
/ 30 января 2010

У меня есть класс (A), который обращается (косвенно через статический метод) к статической переменной (контейнеру STL) в другом классе (B) в своем конструкторе и деструкторе.

Объекты могут быть глобальными, глобальными константами, статическими членами другого класса, храниться в других классах (которые сами могут иметь глобальные или статические экземпляры) или, в принципе, где-либо еще, объект c ++ может быть.

Если объект A создается до статических элементов в B или разрушается после статических элементов в B, в какой-то момент это вызовет сбой (обычно нарушение доступа).

Есть ли какой-нибудь способ гарантировать, что все экземпляры класса A (кроме тех, которые просочились, поскольку по определению там "потеряно" и, следовательно, не будут уничтожены каким-либо образом) будут созданы после и уничтожены перед статической переменной B?

Я видел некоторые решения для создания / разрушения определенной переменной до / после другой, однако это не общий случай для всех экземпляров данного типа, поэтому я не уверен, как к этому подойти.

Ответы [ 6 ]

9 голосов
/ 30 января 2010

Нет.Это известно как фиаско статической инициализации .Порядок, в котором объекты создаются до входа в main, не указан.Единственная гарантия, что это произойдет.

То, что вы можете сделать, - это ленивая инициализация.Это означает, что ваши объекты не будут инициализированы, пока вы их не используете.Например:

struct A { /* some data */ };
struct B { B(void){ /* get A's data */ } };

A& get_A(void)
{
    static A instance;
    return instance;
}

B& get_B(void)
{
    static B instance;
    return instance;
}

Вы используете get_A и get_B для получения глобальных экземпляров.Часть, где B использует A, должна использовать get_A, а использование B должно быть с get_B.Обратите внимание, что get_B является необязательным в вашем случае.

Что происходит при первом создании B?(Либо глобально, либо в функции). Конструктор вызовет get_A и , это , где будет создано A.Это позволит вам контролировать порядок построения вещей.

Примечание. Я думаю, что я поменял местами ваши А и Б.

1 голос
/ 30 января 2010

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

1 голос
/ 30 января 2010

Книга "Современный дизайн C ++" хорошо освещает эту проблему.

В Google Книгах содержится большая часть сканов - см. Раздел 6.5 (стр. 135) - ссылка .

1 голос
/ 30 января 2010

В общем, такого метода нет. Однако есть обходные пути. Вы можете получить объект с глобальной областью действия и глобальным временем жизни чуть меньше, если иметь глобальный указатель и инициализировать / уничтожить его в main / WinMain. Кроме того, вы помещаете свое глобальное состояние, которое будет уничтожено последним, в объект кучи с пересчетом.

Также рассмотрим редизайн:)

0 голосов
/ 08 декабря 2018

В случае, если вы используете ленивые синглтоны (которые возвращают статику, созданную по требованию), у вас может возникнуть возможность, когда один синглтон использует другой после того, как он уже был удален. Например, представьте, что у вас есть глобальный HttpClient синглтон, который позволяет вам выполнять http-запросы. Кроме того, вы, вероятно, хотите иметь ведение журнала, которое может быть предоставлено Log singleton:

class HttpClient
{
    ...
    static HttpClient& singleton()
    {
        static HttpClient http;
        return http;
    }
};

То же самое для Log синглтона. Теперь представьте, что конструктор и деструктор HttpClient просто регистрируют, что этот HttpClient был создан и удален. В этом случае деструктор HttpClient может в конечном итоге использовать удаленный Log singleton.

Пример кода :

#include <stdio.h>

class Log
{
    Log()
    {
        msg("Log");
    }

    ~Log()
    {
        msg("~Log");
    }

public:    
    static Log& singleton()
    {
        static Log log;
        return log;
    }

    void msg(const char* str)
    {
        puts(str);
    }
};

class HttpClient
{
    HttpClient()
    {
        Log::singleton().msg("HttpClient");
    }
    ~HttpClient()
    {
        Log::singleton().msg("~HttpClient");
    }

public:
    static HttpClient& singleton()
    {
        static HttpClient http;
        return http;
    }
    void request()
    {
        Log::singleton().msg("HttpClient::request");
    }
};

int main()
{
    HttpClient::singleton().request();
}

и вывод:

 Log
 HttpClient
 HttpClient::request
 ~HttpClient
 ~Log

Пока все правильно, просто потому, что случилось, что Log был построен до HttpClient, что означает, что HttpClient все еще может использовать Log в своем деструкторе. Теперь просто закомментируйте код регистрации в конструкторе HttpClient, и вы получите этот вывод :

Log
HttpClient::request
~Log
~HttpClient

Как видите, журнал используется после того, как его деструктор ~Log уже был вызван. Как уже отмечалось, деглобализация может быть лучшим подходом, но если вы хотите использовать созданные по требованию синглтоны и заставить некоторые из них жить дольше, чем другие, то вы можете заставить такие синглеты использовать глобальную статическую инициализацию по запросу . Я часто использую этот подход в производственном коде:

class Log
{
    friend std::unique_ptr<Log>::deleter_type;
    ...
    static std::unique_ptr<Log> log;

    static Log& createSingleton()
    {
        assert(!log);
        log.reset(new Log);
        return *log;
    }

public:    
    static Log& singleton()
    {
        static Log& log = createSingleton();
        return log;
    }
};

std::unique_ptr<Log> Log::log;

Теперь, независимо от порядка, в котором эти синглтоны были построены, порядок уничтожения будет гарантировать, что Log будет уничтожен после HttpClient. Это, однако, все равно может завершиться сбоем и привести к неожиданному выводу, если HttpClient был использован из глобального статического конструктора. Или, если вы хотите иметь несколько таких «супер» глобалов (например, Log и Config), которые используют друг друга в случайном порядке, у вас все равно останутся эти проблемы. В таких случаях иногда лучше выделить один раз в кучу, никогда не удаляйте некоторые из этих объектов.

0 голосов
/ 30 января 2010

Как отмечали другие, не существует стандартного и переносимого способа решения этой проблемы из-за проблемы Статический порядок инициализации fiasco .

Тем не менее, вы должны быть в состоянии решить вашу проблему, применив немного замысла, поэтому вы получите степень контроля, когда (и как) объекты A и B создаются. Взгляните на шаблоны проектирования, такие как шаблон творчества Singleton , во многих (если не в большинстве случаев) он рассматривается как анти-шаблон , несмотря на то, что его стоит изучить Также обратите внимание на паттерн Monostate , который может быть использован как лучший синглтон. Эти шаблоны могут помочь управлять созданием объекта и временем его жизни, поэтому все вещи должным образом инициализируются перед использованием.

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

...