Синглтон-деструкторы

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

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
27
0
25 860
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

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

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

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

Ответ принят как подходящий

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

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

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

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

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

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

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

Если вы полагаетесь на библиотеку времени выполнения, уничтожающую ваши статические объекты после возврата из основного, и вы надеетесь, что можно будет использовать код в (Windows) DLL, значит, вы запускаете код во время DllMain, и большинство вещей, которые вам могут понравиться небезопасны.

Integer Poet 14.08.2010 00:32

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

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

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

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

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

+1 действительно верно, но некоторые синглтоны обеспечивают вызов деструктора при выходе из приложения.

Nicola Bonelli 08.11.2008 14:33

Если вы полагаетесь на библиотеку времени выполнения, уничтожающую ваши статические объекты после возврата из основного, и вы надеетесь, что можно будет использовать код в (Windows) DLL, значит, вы запускаете код во время DllMain, и большинство вещей, которые вам могут понравиться небезопасны.

Integer Poet 14.08.2010 00:31

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

В примере C++ с использованием типичной идиомы singleton:

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

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

Но что произойдет, если рабочий поток вызовет get_singleton() после того, как основной поток отключил singleton?

Ben 21.09.2018 19:25

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

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

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;
}

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

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

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

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

#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

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

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

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

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

Integer Poet 14.08.2010 00:26

Кроме того, программисты на C++ не получают бесплатного прохода, даже если они хорошо разбираются в RAII, особенно в контексте синглтонов. Если вы полагаетесь на библиотеку времени выполнения, уничтожающую ваши статические объекты после возврата из основного, и вы надеетесь, что можно будет использовать код в (Windows) DLL, значит, вы запускаете код во время DllMain, и большинство вещей, которые вам могут понравиться небезопасны.

Integer Poet 14.08.2010 00:28

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

Thorsten79 18.08.2010 12:58

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

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

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

Интересный ответ. Хорошая идея также установить для Singleton::ptr нулевое значение.

Joshua Breeden 10.07.2018 04:37

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

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, каждый поток по-прежнему имеет свой собственный localshared_ptr, который поддерживает работу одного Singleton.

Другие вопросы по теме