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

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

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

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

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

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


person Fire Lancer    schedule 29.01.2010    source источник


Ответы (6)


Нет. Это известно как фиаско статической инициализации. . Порядок построения объектов до входа в 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. Это позволяет вам контролировать порядок построения вещей.

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

person GManNickG    schedule 29.01.2010
comment
Он спрашивает об уничтожении, а не об инициализации. - person ; 30.01.2010
comment
@Neil: Однако порядок уничтожения определяется порядком инициализации. - person GManNickG; 30.01.2010
comment
Итак, я могу просто изменить свой статический контейнер-контейнер на статический Container& getContainer(){static Container container;return container;} и он будет освобожден после всего, что прямо или косвенно вызывает getContainer в его конструкторе? Что, если экземпляр A был сохранен в глобальном shared_ptr в какой-то момент позже в программе? - person Fire Lancer; 30.01.2010
comment
Вы по-прежнему навлекаете на себя неприятности, как указано в предыдущей записи часто задаваемых вопросов по C++ (parashift.com/c++-faq-lite/ctors.html#faq-10.14) упоминает о статических объектах и ​​статических указателях. - person jamesdlin; 30.01.2010

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

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

person Seva Alekseyev    schedule 29.01.2010

Книга "Modern C++ Design" охватывает этот вопрос красиво.

Google Книги содержат сканы большей части книг — см. раздел 6.5 (стр. 135) — ссылка.

person stusmith    schedule 29.01.2010

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

person EvilTeach    schedule 30.01.2010

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

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

Как правило, рекомендуется избегать использования глобалов — придерживаться деглобализации.

person mloskot    schedule 30.01.2010

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

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

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

Пример кода:

#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), которые используют друг друга в случайном порядке, у вас все еще будет эта проблема. В таких случаях иногда лучше выделить один раз в куче, никогда не удаляя некоторые из этих объектов.

person Pavel P    schedule 08.12.2018
comment
(просто примечание, я знаю, что этому вопросу почти десять лет, просто хотел опубликовать пример кода на случай, если кто-то встретится) - person Pavel P; 08.12.2018