Qt C ++: Глобальные объекты против цепочки ссылок - PullRequest
3 голосов
/ 30 июня 2010

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

У меня есть 3 класса (регистратор, настройки и контейнер для некоторых временных данных)что мне нужно получить доступ через несколько различных объектов.В настоящее время все они созданы с использованием шаблона синглтона.По сути, регистратор - это всего лишь один открытый метод Log () с некоторой внутренней логикой, когда параметры и контейнер имеют несколько методов get / set с некоторой дополнительной логикой (например, QFileSystemWatcher).Кроме того, логгер и настройки имеют некоторую перекрестную ссылку (например, логгеру нужны некоторые ошибки настроек и журналов настроек).

В настоящее время все «работает нормально», но все же есть некоторые проблемы, о которых следует позаботиться икажется, что их нелегко реализовать для синглетонов (возможные утечки памяти / нулевые указатели).Теперь у меня есть два разных способа справиться с этим:

  1. Создать глобальные объекты (например, extern Logger log;) и инициализировать их при запуске приложения.
  2. Создайте объекты в моем главном объекте и передайте их детям в качестве ссылки.

Как у меня есть несколько вопросов, связанных с ними:

Case1.

  • Лучше использовать стек или кучу?
  • Я собираюсь объявить эти объекты в некотором заголовке globals.h, используя ключевое слово extern.Это нормально?
  • Я думаю, что в этом случае мне нужно удалить эту двухстороннюю ссылку (для настройки нужен регистратор и наоборот.)?

Случай 2.

  • Должны ли объекты быть созданы в стеке или куче в моем главном объекте (например, Logger * log = new Logger () vs Logger log;)

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

  • Как насчет детей?

    1. Если я передам указатель на дочерние элементы, подобные этому (я не хочу копировать его, просто используйте «ссылку»): Children (Logger * log): m_Log (log), что произойдеткогда дети удаляются?Должен ли я установить локальный указатель m_Log в NULL или?
    2. Если я использую стек, я отправлю ссылку на ребенка (Children (Logger & log): m_Log (log)), где m_Log - ссылочная переменная (Logger & m_Log;), верно?
  • Что я должен отметить с точки зрения управления памятью Qt в этом случае?

Случай 3. Продолжить с одноэлементным и инициализировать одноэлементные объектыво время запуска (это решило бы нулевые указатели).Тогда единственной проблемой будут возможные утечки памяти.Моя реализация следует этому примеру.Есть ли возможная утечка памяти при доступе к классу с помощью.Как насчет уничтожения синглтона?#define LOG Logger :: Instance () -> Log

Спасибо за чтение.

Ответы [ 2 ]

1 голос
/ 30 июня 2010

Сводка в простых терминах:

  1. если вы используете глобальные объекты, предпочтите шаблон синглтона как меньшее зло.Обратите внимание, что синглтон должен иметь глобальный доступ! Решение Dan-O на самом деле не является одноэлементным шаблоном, и оно побеждает силу синглетонов, даже если он предполагает, что оно ничем не отличается.
  2. если вы используете глобальные объекты, используйте ленивую конструкцию, чтобы избежать порядка инициализациипроблемы (инициализируйте их при первом обращении).
  3. если вы используете синглтоны, вместо того, чтобы делать все, что должно быть глобально доступным, синглтоном, рассмотрите возможность создания одного синглтона (приложения), в котором хранятся другие глобально доступные объекты.(Logger, Settings и т. Д.), Но не делайте эти объекты одиночными.
  4. если вы используете локальных пользователей, рассмотрите # 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 выше.

0 голосов
/ 01 июля 2010

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

Лучше использовать стек или кучу?

Предполагая, что под "стеком" вы подразумевали глобальность (и, следовательно, в сегменте данных), не беспокойтесь об этом, делайте, что вам проще. Помните, что если вы размещаете его в куче, вам нужно вызвать delete.

Я собираюсь объявить эти объекты в некотором заголовке globals.h, используя ключевое слово extern. Это нормально?

Зачем вам это нужно? Единственные классы, которым нужен доступ к глобалам, - это сами синглтоны. Глобальные переменные могут быть даже статическими локальными переменными, ala:

class c_Foo
{
    static c_Foo& Instance()
    {
        static c_Foo g_foo; // static local variable will live for full life of the program, but cannot be accessed elsewhere, forcing others to use cFoo::Instance()
        return g_foo;
    }
 };

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

Помните, вы хотите, чтобы время жизни класса было «глобальным» (т.е. не уничтожалось до выхода из приложения), а не самого экземпляра.

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

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

Должны ли объекты быть созданы в стеке или куче в моем главном объекте (например, Logger * log = new Logger () vs Logger log;)

Я не могу ответить на этот вопрос, не беспокойтесь об этом снова.

Длинные цепочки ссылок выглядят не очень красиво (например, если я должен передать объект над несколькими детьми).

А у детей?

Замечательно, вы поняли, что это будет огромная боль в заднице. В случае чего-то вроде логгера было бы адски передавать ссылки на каждый модуль, чтобы они могли выплевывать информацию логирования. Более подходящей была бы одиночная статическая функция «Log» ala C, если ваш регистратор имеет полезное состояние, то сделайте его одиночным, который будет виден только вашей функции журнала. Вы можете объявить и реализовать весь ваш класс logger в том же файле .cpp, в котором вы реализуете свою функцию log. Тогда больше ничего не будет известно об этой специальной функции.

Вот что я имею в виду:

файл: log.h

#ifndef LOG_H
#define LOG_H

void Log( const char* data ); // or QString or whatever you're passing to your logger

#endif//LOG_H

файл: log.cpp

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

Это прекрасный интерфейс логгера, вся мощь вашего синглтона, ограниченная суета.

Возможна ли утечка памяти при доступе к классу с помощью. А как насчет уничтожения синглтона?

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

Существуют несколько схожие риски, если вы используете статический локальный: http://blogs.msdn.com/b/oldnewthing/archive/2004/03/08/85901.aspx

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

...