Сводка в простых терминах:
- если вы используете глобальные объекты, предпочтите шаблон синглтона как меньшее зло.Обратите внимание, что синглтон должен иметь глобальный доступ! Решение Dan-O на самом деле не является одноэлементным шаблоном, и оно побеждает силу синглетонов, даже если он предполагает, что оно ничем не отличается.
- если вы используете глобальные объекты, используйте ленивую конструкцию, чтобы избежать порядка инициализациипроблемы (инициализируйте их при первом обращении).
- если вы используете синглтоны, вместо того, чтобы делать все, что должно быть глобально доступным, синглтоном, рассмотрите возможность создания одного синглтона (приложения), в котором хранятся другие глобально доступные объекты.(Logger, Settings и т. Д.), Но не делайте эти объекты одиночными.
- если вы используете локальных пользователей, рассмотрите # 3 в любом случае, чтобы избежать необходимости передавать так много вещей вашей системе.
[Редактировать] Я допустил ошибку и не поместил статический элемент в safe_static, который указал Дэн.Спасибо ему за это.Я был на мгновение слепым и не осознавал ошибки, основываясь на вопросах, которые он задавал, что приводило к самой неловкой ситуации.Я пытался объяснить ленивое конструирование (иначе говоря, ленивая загрузка) поведения синглетонов, и он не следил за тем, чтобы я допустил ошибку, и до сих пор не осознал, что сделал такую ошибку до следующего дня.Я не заинтересован в аргументации, я лишь предоставляю лучший совет, но я должен настоятельно рекомендовать некоторые советы, особенно в этом случае:
#include "log.h"
// declare your logger class here in the cpp file:
class Logger
{
// ... your impl as a singleton
}
void Log( const char* data )
{
Logger.getInstance().DoRealLog( data );
}
Если вы собираетесь использовать глобально доступные объекты, такие как синглтоны, то хотя бы избегай этого!Он может иметь привлекательный синтаксис для клиента, но он идет вразрез со многими проблемами, которые одиночные компании пытаются смягчить.Вам нужен общедоступный одноэлементный экземпляр, и если вы создаете такую функцию журнала, вы хотите передать ей свой одноэлементный экземпляр.Для этого есть много причин, но здесь есть только один сценарий: вы можете создать отдельные синглтоны Logger с общим интерфейсом (например, журнал ошибок и журнал предупреждений или журнал сообщений пользователя).Этот метод не позволяет клиенту выбирать и использовать общий интерфейс ведения журнала.Это также заставляет экземпляр синглтона извлекаться каждый раз, когда вы что-то регистрируете, что делает его таким, что если вы когда-нибудь решите уклониться от синглетонов, будет гораздо больше кода для перезаписи.
Создатьглобальные объекты (например, extern Logger log;) и инициализируйте их при запуске приложения.
Старайтесь избегать этого любой ценой для пользовательских типов, по крайней мере.Предоставление объекту внешней связи означает, что ваш регистратор будет создан до главной точки входа, и если он зависит от любых других глобальных данных, подобных этому, нет никакой гарантии относительно порядка инициализации (ваш регистратор может получать доступ к неинициализированным объектам).
Вместо этого рассмотрим этот подход, когда доступ является инициализацией:
Logger& safe_static()
{
static Logger logger;
return logger;
}
Или в вашем случае:
// Logger::instance is a static method
Logger& Logger::instance()
{
static Logger logger;
return logger;
}
В этой функции регистратор не будет создан до тех пор, пока не будет создан метод safe_staticназывается.Если вы примените это ко всем подобным данным, вам не нужно беспокоиться о порядке инициализации, поскольку порядок инициализации будет соответствовать шаблону доступа.
Обратите внимание, что, несмотря на его имя, он небезопасен.Это по-прежнему подвержено проблемам, связанным с потоками, если два потока одновременно вызывают safe_static в первый раз одновременно.Один из способов избежать этого - вызывать эти методы в начале вашего приложения, чтобы гарантировать гарантированную инициализацию данных после запуска.
Создайте объекты в моем главном объекте и передайте их дочерним элементам какссылка.
Это может стать громоздким и значительно увеличить размер кода, чтобы обойти несколько объектов таким образом.Подумайте о том, чтобы объединить эти объекты в один агрегат, содержащий все необходимые контекстные данные.
Лучше использовать стек или кучу?
С общей точки зрения, если ваши данные невелики и могут удобно помещаться в стеке, стек обычно предпочтительнее.Распределение / освобождение стека происходит очень быстро (просто увеличивает / уменьшает регистр стека) и не имеет проблем с конфликтом потоков.
Однако, поскольку вы задаете этот вопрос специально для глобальных объектов, стек нене имеет особого смысла.Возможно, вы спрашиваете, следует ли вам использовать кучу или сегмент данных.Последнее подходит для многих случаев и не страдает от проблем с утечкой памяти.
Я собираюсь объявить эти объекты в некоторых заголовках globals.h, используя ключевое слово extern.Это нормально?
Нет.@ см. safe_static выше.
Я думаю, что в этом случае мне нужно удалить эту двустороннюю ссылку (для настройки нужен регистратор и наоборот.)?
Это всегда хорошочтобы попытаться исключить циклические зависимости из вашего кода, но если вы не можете, @see safe_static.
Если я передам указатель на детей, как это (я не хочу его копировать,просто используйте "reference"): Children (Logger * log): m_Log (log), что происходит при удалении дочерних элементов?Должен ли я установить локальный указатель m_Log в NULL или?
Нет необходимости делать это.Я предполагаю, что управление памятью для логгера не имеет отношения к ребенку.Если вы хотите более надежное решение, вы можете использовать boost :: shared_ptr и подсчет ссылок для управления временем жизни регистратора.
Если я использую стек, я отправлю ссылку на дочерний элемент (Children (Logger & log): m_Log (log)) где m_Log - ссылочная переменная (Logger & m_Log;), верно?
Вы можете передавать по ссылке независимо от того, используете ли вы стек или кучу.Однако хранение указателей как элементов над ссылками дает то преимущество, что компилятор может генерировать значимый оператор присваивания (если это применимо) в тех случаях, когда это желательно, но вам не нужно явно определять его самостоятельно.
Случай 3. Продолжайте использовать singleton и инициализировать одноэлементные объекты во время запуска (это решило бы нулевые указатели).Тогда единственной проблемой будут возможные утечки памяти.Моя реализация следует этому примеру.Есть ли возможная утечка памяти при доступе к классу с помощью.А как насчет уничтожения синглтона?
Используйте boost :: scoped_ptr или просто храните ваши классы как статические объекты внутри функции доступа, как в safe_static выше.