Деструкторы синглтона

Должны ли объекты Singleton, которые не используют счетчики экземпляров/ссылок, считаться утечками памяти в C++?

Без счетчика, который вызывает явное удаление экземпляра синглтона, когда счетчик равен нулю, как удаляется объект? Очищается ли ОС при завершении работы приложения? Что, если бы этот синглтон выделил память в куче?

В двух словах, должен ли я вызывать деструктор Singelton или я могу полагаться на его очистку, когда приложение завершает работу?


person Bryan Marble    schedule 07.11.2008    source источник


Ответы (12)


Вы можете быть уверены, что операционная система очистит его.

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

Если вы используете синглтон, который лениво выделяется (т. е. с идиомой блокировки с тройной проверкой) на таком языке, как c++, с реальными деструкторами, а не с финализаторами, вы не можете полагаться на то, что его деструктор вызывается во время завершения работы программы. Если вы используете один статический экземпляр, то деструктор запустится после завершения main в какой-то момент.

Независимо от того, когда процесс завершается, вся память возвращается в операционную систему.

person Edward KMETT    schedule 07.11.2008

Как это часто бывает, «это зависит». В любой операционной системе, достойной этого названия, когда ваш процесс завершается, вся память и другие ресурсы, используемые локально внутри процесса, БУДЕТ освобождены. Вам просто не нужно беспокоиться об этом.

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

RAII поможет вам здесь. Если у вас такой сценарий:

class Tempfile
{
Tempfile() {}; // creates a temporary file 
virtual ~Tempfile(); // close AND DELETE the temporary file 
};

Tempfile &singleton()
{
  static Tempfile t;
  return t;
}

...тогда вы можете быть уверены, что ваш временный файл БУДЕТ закрыт и удален при завершении работы вашего приложения. Однако это НЕ потокобезопасно , и порядок удаления объектов может отличаться от ожидаемого или требуемого.

однако, если ваш синглтон реализован как ЭТО

Tempfile &singleton()
{
  static Tempfile *t = NULL;
  if (t == NULL)
    t = new Tempfile(); 
  return *t;
}

...тогда у вас другая ситуация. Память, используемая вашим временным файлом, будет восстановлена, но файл НЕ будет удален, потому что деструктор не будет вызван.

person Roddy    schedule 08.11.2008

Вы должны явно очистить все свои объекты. Никогда не полагайтесь на то, что ОС уберет за вас.

Где я обычно использую синглтон, так это для инкапсуляции управления чем-то вроде файла, аппаратного ресурса и т. д. Если я не очистил это соединение должным образом, я могу легко утечь системные ресурсы. При следующем запуске приложения может произойти сбой, если ресурс все еще заблокирован предыдущей операцией. Другая проблема может заключаться в том, что любая финализация, например запись буфера на диск, может не произойти, если она все еще существует в буфере, принадлежащем экземпляру singleton.

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

person Klathzazt    schedule 07.11.2008
comment
+1 действительно верно, но некоторые синглтоны обеспечивают вызов деструктора при выходе из приложения. - person Nicola Bonelli; 08.11.2008
comment
Если вы полагаетесь на то, что библиотека времени выполнения уничтожает ваши статические объекты после основного возврата, и вы надеетесь, что можно будет использовать код в (Windows) DLL, тогда вы запускаете код во время DllMain, и большинство вещей, которые вы, возможно, захотите сделать небезопасны. - person Integer Poet; 14.08.2010

Каждый язык и среда будут отличаться, хотя я согласен с @Aaron Fisher в том, что синглтон имеет тенденцию существовать на протяжении всего процесса.

В примере C++, используя типичную одноэлементную идиому:

Singleton &get_singleton()
{
   static Singleton singleton;
   return singleton;
}

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

person Don Wakefield    schedule 07.11.2008
comment
Но что произойдет, если рабочий поток вызовет get_singleton() после того, как основной поток разрушит singleton? - person Ben; 21.09.2018

Как вы создаете объект?

Если вы используете глобальную переменную или статическую переменную, деструктор будет вызван, предполагая, что программа завершается нормально.

Например, программа

#include <iostream>

class Test
{
    const char *msg;

public:

    Test(const char *msg)
    : msg(msg)
    {}

    ~Test()
    {
        std::cout << "In destructor: " << msg << std::endl;
    }
};

Test globalTest("GlobalTest");

int main(int, char *argv[])
{
    static Test staticTest("StaticTest");

    return 0;
}

Распечатывает

In destructor: StaticTest 
In destructor: GlobalTest
person David Norman    schedule 08.11.2008

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

Кроме того, типичная реализация синглтона, такая как синглтон Мейерса, не только является потокобезопасной во время инициализации при первом вызове, но также гарантирует изящное завершение при выходе из приложения (вызывается деструктор).

В любом случае, если приложению отправляется сигнал unix (например, SIGTERM или SIGHUP), поведение по умолчанию заключается в завершении процесса без вызова деструкторов статически выделенных объектов (синглетонов). Чтобы решить эту проблему для этих сигналов, можно установить обработчик, вызывающий выход, или удалить выход таким обработчиком -- signal(SIGTERM,exit);

person Nicola Bonelli    schedule 07.11.2008
comment
Если вы полагаетесь на то, что библиотека времени выполнения уничтожает ваши статические объекты после основного возврата, и вы надеетесь, что можно будет использовать код в (Windows) DLL, тогда вы запускаете код во время DllMain, и большинство вещей, которые вы, возможно, захотите сделать небезопасны. - person Integer Poet; 14.08.2010

Это фольклор явно освобождать глобальные выделения памяти до завершения работы приложения. Я полагаю, что большинство из нас делают это по привычке и потому, что нам кажется, что «забыть» о структуре нехорошо. В мире C существует закон симметрии, согласно которому любое выделение должно где-то освобождаться. Программисты на C++ думают по-другому, если знают и практикуют RAII.

В старые добрые времена, например. AmigaOS были НАСТОЯЩИЕ утечки памяти. Если вы забудете освободить память, она НИКОГДА не станет снова доступной, пока система не будет перезагружена.

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

person Thorsten79    schedule 08.11.2008
comment
Это не фольклор, если вы пишете повторно используемый код как часть усилий по разработке приложений. Если вы не знаете всех будущих контекстов, в которых будет использоваться код, который, как вы надеетесь, будет повторно использоваться, то лучше ошибиться в сторону осторожности. Мои собственные проекты, как правило, представляют собой код, который я надеюсь повторно использовать с тонким слоем логики приложения. - person Integer Poet; 14.08.2010
comment
Кроме того, программисты на C++ не получают бесплатного доступа, даже если они хорошо разбираются в RAII, особенно в контексте синглтонов. Если вы полагаетесь на то, что библиотека времени выполнения уничтожает ваши статические объекты после основного возврата, и вы надеетесь, что можно будет использовать код в (Windows) DLL, тогда вы запускаете код во время DllMain, и большинство вещей, которые вы, возможно, захотите сделать небезопасны. - person Integer Poet; 14.08.2010
comment
Вы правы для повторно используемого кода внутри библиотек. Но не для приложений. В любой современной операционной системе нет утечек памяти за пределами глобального кода (т.е. библиотек). Таких просто не существует. Вся арена памяти уничтожается, когда приложение завершает работу. Все new() и malloc() превращаются в пыль, независимо от free() или delete(). - person Thorsten79; 18.08.2010

Зависит от вашего определения утечки. Несвязанное увеличение памяти - это утечка в моей книге, синглтон не несвязанный. Если вы не обеспечиваете подсчет ссылок, вы намеренно сохраняете экземпляр. Не авария, не утечка.

Деструктор вашей одноэлементной оболочки должен удалить экземпляр, это не происходит автоматически. Если он просто выделяет память и не использует ресурсы ОС, в этом нет смысла.

person Hans Passant    schedule 07.11.2008

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

person Aaron Fischer    schedule 07.11.2008

В таких языках, как C++, в которых нет сборки мусора, рекомендуется выполнять очистку перед завершением. Вы можете сделать это с помощью класса друзей-деструкторов.

class Singleton{
...
   friend class Singleton_Cleanup;
};
class Singleton_Cleanup{
public:
    ~Singleton_Cleanup(){
         delete Singleton::ptr;
     }
};

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

person Dan Lin    schedule 21.12.2016
comment
Интересный ответ. Также рекомендуется установить Singleton::ptr в нулевое значение. - person Joshua Breeden; 10.07.2018

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

* Это не означает, что вы должны обходить новые указатели и никогда не очищать их.

person Marcin    schedule 07.11.2008

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

Singleton &get_singleton() {
   static Singleton singleton;
   return singleton;
}

я думаю

Singleton &get_singleton() {
   static std::shared_ptr<Singleton> singleton = std::make_shared<Singleton>();
   static thread_local std::shared_ptr<Singleton> local = singleton;
   return *local;
}

поэтому, когда основной поток выходит и забирает с собой singleton, каждый поток по-прежнему имеет свой собственный local shared_ptr, который сохраняет жизнь Singleton.

person Ben    schedule 21.09.2018