Недавно я столкнулся с реализацией / реализацией шаблона проектирования Singleton для C ++. Это выглядело так (я взял это из примера из реальной жизни):
// a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
};
Из этого объявления я могу сделать вывод, что поле экземпляра инициируется в куче. Это означает, что есть выделение памяти. Что для меня совершенно неясно, когда именно память будет освобождена? Или есть ошибка и утечка памяти? Кажется, что есть проблема в реализации.
Мой главный вопрос: как мне правильно это реализовать?
c++
design-patterns
singleton
Артем Баргер
источник
источник
Ответы:
В 2008 году я предоставил реализацию C ++ 98 шаблона проектирования Singleton, который лениво оценивается, гарантированно уничтожается, не является технически безопасным для потоков:
может ли кто-нибудь предоставить мне образец Singleton в c ++?
Вот обновленная реализация C ++ 11 шаблона проектирования Singleton, которая вычисляется лениво, корректно уничтожается и является поточно-ориентированной .
См. Эту статью о том, когда использовать синглтон: (не часто)
Синглтон: как его использовать
См. Эту две статьи о порядке инициализации и о том, как справиться:
Порядок инициализации статических переменных
Поиск проблем с порядком статической инициализации C ++
В этой статье описывается время жизни:
каково время жизни статической переменной в функции C ++?
См. Эту статью, в которой обсуждаются некоторые последствия потоков для синглетонов:
экземпляр Singleton, объявленный как статическая переменная метода GetInstance, является ли он потокобезопасным?
См. Эту статью, в которой объясняется, почему блокировка с двойной проверкой не будет работать на C ++:
каковы все распространенные неопределенные поведения, о которых должен знать программист C ++?
Доктор Доббс: C ++ и опасности двойной проверки блокировки: часть I
источник
What irks me most though is the run-time check of the hidden boolean in getInstance()
Это предположение о технике реализации. Там не должно быть никаких предположений о том, что он жив. см. stackoverflow.com/a/335746/14065 Вы можете форсировать ситуацию, чтобы она всегда была живой (меньше накладных расходов, чемSchwarz counter
). Глобальные переменные имеют больше проблем с порядком инициализации (между единицами компиляции), поскольку вы не форсируете порядок. Преимуществом этой модели является 1) ленивая инициализация. 2) Способность навести порядок (Шварц помогает, но уродливее). Даget_instance()
гораздо страшнее.Будучи Синглтоном, вы обычно не хотите, чтобы он был разрушен.
Когда программа завершится, она будет снесена и освобождена, что является нормальным желаемым поведением для синглтона. Если вы хотите иметь возможность явно очистить его, довольно легко добавить статический метод к классу, который позволит вам восстановить его в чистое состояние и перераспределить его в следующий раз, когда он будет использоваться, но это выходит за рамки "классический" синглтон.
источник
Вы можете избежать выделения памяти. Вариантов много, у всех проблемы в случае многопоточности.
Я предпочитаю этот вид реализации (на самом деле, я не правильно сказал, что предпочитаю, потому что максимально избегаю синглетонов):
У него нет динамического выделения памяти.
источник
Ответ @Loki Astari отличный.
Однако бывают случаи с несколькими статическими объектами, когда вам нужно быть в состоянии гарантировать, что синглтон не будет уничтожен, пока все ваши статические объекты, которые используют синглтон, больше не будут в этом нуждаться.
В этом случае
std::shared_ptr
можно использовать, чтобы сохранить синглтон для всех пользователей, даже когда в конце программы вызываются статические деструкторы:источник
Другая нераспределительная альтернатива: создайте синглтон, скажем, класса
C
, как вам это нужно:с помощью
Ни этот, ни ответ Кэтэлина не являются автоматически поточно-ориентированными в текущем C ++, но будут в C ++ 0x.
источник
Я не нашел реализацию CRTP среди ответов, поэтому вот она:
Чтобы использовать просто наследовать ваш класс от этого, например:
class Test : public Singleton<Test>
источник
Решение в принятом ответе имеет существенный недостаток - деструктор для синглтона вызывается после того, как элемент управления покидает
main()
функцию. На самом деле могут быть проблемы, когда некоторые зависимые объекты размещены внутриmain
.Я столкнулся с этой проблемой, когда пытался ввести Singleton в приложении Qt. Я решил, что все мои диалоговые окна настройки должны быть Singletons, и принял шаблон выше. К сожалению, основной класс Qt
QApplication
был размещен в стеке вmain
функции, и Qt запрещает создавать / уничтожать диалоги, когда объект приложения недоступен.Вот почему я предпочитаю кучу выделенных синглетонов. Я предоставляю явные
init()
иterm()
методы для всех синглетонов и вызываю их внутриmain
. Таким образом, я полностью контролирую порядок создания / уничтожения синглетонов, а также гарантирую, что синглтоны будут созданы, независимо от того, кто-то звонилgetInstance()
или нет.источник
Вот простая реализация.
Создан только один объект, и эта ссылка на объект возвращается каждый раз после слов.
Здесь 00915CB8 - это место в памяти одноэлементного объекта, одинаковое для продолжительности программы, но (обычно!) Разное при каждом запуске программы.
NB Это не потокобезопасный. Вы должны обеспечить безопасность потока.
источник
Если вы хотите разместить объект в куче, почему бы не использовать уникальный указатель. Память также будет освобождена, так как мы используем уникальный указатель.
источник
m_s
локальныйstatic
изgetInstance()
и инициализировать его сразу же без теста.Это действительно, вероятно, выделено из кучи, но без источников невозможно узнать.
Типичная реализация (взятая из некоторого кода, который у меня уже есть в emacs):
... и рассчитывать на то, что программа выйдет из области видимости после очистки.
Если вы работаете на платформе, где очистка должна выполняться вручную, я бы, вероятно, добавил процедуру ручной очистки.
Еще одна проблема, связанная с этим, заключается в том, что он не является потокобезопасным. В многопоточной среде два потока могут пройти через «если», прежде чем любой из них сможет выделить новый экземпляр (так будут оба). Это все еще не так уж сложно, если вы все равно рассчитываете на завершение программы.
источник
Кто-нибудь упоминал
std::call_once
иstd::once_flag
? Большинство других подходов, включая двойную проверку блокировки, не работают.Одной из основных проблем в реализации одноэлементного шаблона является безопасная инициализация. Единственный безопасный способ - защитить последовательность инициализации с помощью синхронизирующих барьеров. Но сами эти барьеры должны быть благополучно инициированы.
std::once_flag
механизм гарантированной безопасной инициализацииисточник
Мы недавно обсуждали эту тему в моем классе EECS. Если вы хотите подробно ознакомиться с примечаниями к лекции, посетите http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf.
Есть два способа, как я знаю, чтобы правильно создать класс Singleton.
Первый путь:
Реализуйте это так же, как в вашем примере. Что касается уничтожения, «синглтоны обычно переносятся на протяжении всего времени выполнения программы; большинство ОС восстанавливают память и большинство других ресурсов после завершения программы, поэтому есть аргумент, чтобы не беспокоиться об этом».
Тем не менее, это хорошая практика для очистки при завершении программы. Следовательно, вы можете сделать это с помощью вспомогательного статического класса SingletonDestructor и объявить его своим другом в Singleton.
Singleton_destroyer будет создан при запуске программы, и "когда программа завершается, все глобальные / статические объекты уничтожаются кодом завершения работы библиотеки времени исполнения (вставленным компоновщиком), поэтому the_destroyer будет уничтожен; его деструктор удалит Singleton, запустив его деструктор «.
Второй путь
Это называется Meyers Singleton, созданный мастером C ++ Скоттом Мейерсом. Просто определите get_instance () по-другому. Теперь вы также можете избавиться от переменной-члена указателя.
Это удобно, поскольку возвращаемое значение является ссылкой, и вы можете использовать
.
синтаксис вместо->
доступа к переменным-членам.«Компилятор автоматически создает код, который сначала создает 's' через объявление, а не после, а затем удаляет статический объект при завершении программы."
Также обратите внимание, что с Meyers Singleton вы «можете попасть в очень сложную ситуацию, если объекты полагаются друг на друга во время завершения - когда Singleton исчезает относительно других объектов? Но для простых приложений это работает хорошо».
источник
В дополнение к другому обсуждению здесь, возможно, стоит отметить, что вы можете иметь глобальность, не ограничивая использование одним экземпляром. Например, рассмотрим случай подсчета ссылок что-то ...
Теперь где-то внутри функции (например,
main
) вы можете сделать:Реферы не должны хранить указатель на свои
Store
потому что эта информация предоставляется во время компиляции. Вам также не нужно беспокоиться о времениStore
жизни, потому что компилятор требует, чтобы он был глобальным. Если действительно есть только один случай,Store
тогда в этом подходе нет накладных расходов; с более чем одним экземпляром компилятор должен быть умным в отношении генерации кода. При необходимости,ItemRef
класс может быть дажеfriend
изStore
(вы можете иметь шаблонных друзей!).Если
Store
сам по себе является шаблонным классом, то ситуация становится более запутанной, но все еще возможно использовать этот метод, возможно, путем реализации вспомогательного класса со следующей сигнатурой:Теперь пользователь может создать
StoreWrapper
тип (и глобальный экземпляр) для каждого глобальногоStore
экземпляра и всегда получать доступ к хранилищам через свой экземпляр оболочки (таким образом, забывая о мрачных деталях параметров шаблона, необходимых для использованияStore
).источник
Речь идет об объекте управления временем жизни. Предположим, у вас есть больше, чем синглтонов в вашем программном обеспечении. И они зависят от синглтона Logger. Предположим, что во время уничтожения приложения другой одноэлементный объект использует Logger для регистрации шагов его уничтожения. Вы должны гарантировать, что Logger должен быть очищен последним. Поэтому, пожалуйста, ознакомьтесь с этой статьей: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf
источник
Моя реализация похожа на реализацию Галика. Разница в том, что моя реализация позволяет совместно используемым указателям очищать выделенную память, в отличие от удержания в памяти до тех пор, пока приложение не будет закрыто и статические указатели не будут очищены.
источник
Ваш код верен, за исключением того, что вы не объявили указатель экземпляра вне класса . Внутренние объявления класса статических переменных не считаются объявлениями в C ++, однако это разрешено в других языках, таких как C # или Java и т. Д.
Вы должны знать, что экземпляр Singleton нам не нужно удалять вручную . Нам нужен один его объект во всей программе, поэтому в конце выполнения программы он будет автоматически освобожден.
источник
В статье, на которую ссылались выше, описан недостаток блокировки с двойной проверкой, заключающийся в том, что компилятор может выделить память для объекта и установить указатель на адрес выделенной памяти до вызова конструктора объекта. Однако в c ++ довольно легко использовать распределители для выделения памяти вручную, а затем использовать вызов конструкции для инициализации памяти. При использовании этого метода блокировка с двойной проверкой работает просто отлично.
источник
Пример:
источник
Простой одноэлементный класс, это должен быть ваш файл класса заголовка
Получите доступ к вашему синглтону так:
источник
Я думаю, что вы должны написать статическую функцию, в которой ваш статический объект будет удален. Вы должны вызвать эту функцию, когда собираетесь закрыть приложение. Это гарантирует, что у вас нет утечки памяти.
источник