Освобождение экземпляра Singleton - PullRequest
1 голос
/ 24 января 2012

У меня такая синглтонная структура:

// Hpp
class Root : public boost::noncopyable
{
    public:
        ~Root();

        static Root &Get();

        void Initialize();
        void Deinitialize();

    private:
        Root();  // Private for singleton purposes
        static Root *mInstance;

        Manager1 *mManager1;
        Manager2 *mManager2;
};


// Cpp
Root *Root::mInstance = nullptr;

Root::Root()
{
    mInstance = this;

    // Managers are using `mInstance` in their constructors
    mManager1 = new Manager1();
    mManager2 = new Manager2();

    mInstance->Initialize();
}

Root::~Root() { delete mManager1; delete mManager2; }

Root &Root::Get()
{
    if (mInstance == nullptr) mInstance = new Root();
    return *mInstance;
}

void Root::Deinitialize()
{
    delete mInstance;
}

А вот использование этого синглтона:

Root::Get();
// Some code calling related to mManager1 and mManager2
Root::Get().Deinitialize();

Вопросы:

  • Это безопасно использовать такую ​​одноэлементную структуру памяти?
  • Как я могу автоматизировать удаление mInstance (вызов dtor вручную). Потому что пользователь может забыть вызвать метод Deinitialize().

Ответы [ 3 ]

3 голосов
/ 24 января 2012

Для однопоточного приложения, к которому после выхода из main() нет доступа к синглтону, вы можете использовать довольно простой подход, который делает все автоматически:

Root& Root::get() {
    static std::unique_ptr<Root> rc(new Root());
    return *rc;
}

static в этом контексте означает, что переменная инициализируется при первом вызове функции, а затем остается на месте. Среда выполнения C ++ организует уничтожение переменной static rc в определенный момент. Для многопоточных приложений, где потоки запускаются до ввода main(), вам нужен другой подход, который гарантирует, что статическая переменная инициализируется только в потоке.

При этом обратите внимание, что я настоятельно рекомендую , а не , чтобы использовать anti -pattern Singleton (также известный как Глобальные данные ). Приведенный выше пример кода не является рекомендацией любого рода! Есть несколько действительных применений, где вы хотите использовать синглтон, большинство применений нет. Все действительные виды использования, которые я видел, используют неизменный Singleton. Изменчивые одноэлементные объекты имеют тенденцию становиться точкой синхронизации и, как и глобальные данные, запутывают используемые данные.

1 голос
/ 24 января 2012

Рассматривали ли вы, что случилось бы, если бы ваш синглтон вообще не был удален?

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

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

  1. Код проще. Меньше вызовов функций, меньше сложности. Не нужно думать, когда должен быть вызван терминатор, и вот что привело вас сюда.
  2. С явной очисткой, поскольку ваше приложение становится все более сложным (и они, как правило, по мере того, как вы продолжаете добавлять функции), вы рискуете, что какой-то другой объект попытается получить доступ к одноэлементной ссылке после того, как он был завершен и уничтожен. Теперь вместо безобидной утечки памяти, которая немедленно устраняется ОС, у вас есть уродливое необработанное диалоговое окно исключений, на которое пользователи могут посмотреть.

Хорошей практикой также является размещение функции Initialize () для синглтона внутри GetInstance () (как продемонстрировано в pdillon3) вместо того, чтобы приложение явно вызывало Initialize (). Вы не указали эту часть, и если ваш проект исполняемый, вы должны быть в порядке с существующим дизайном. Но будьте осторожны, если вы поместите этот код в DLL. Некоторые люди считают, что DllMain - это хорошее место для инициализации одноэлементных объектов, но это не так. Во время DllMain глобальная критическая секция блокировки загрузчика блокируется, и одиночные инициализации, как правило, вызывают всевозможные проблемы.

Кроме того, теперь вместо 3 методов в вашем одноэлементном интерфейсе у вас есть только один: GetInstance (), красивый и простой.

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

1 голос
/ 24 января 2012

Если вы не используете Visual Studio или C ++ 11, у вас не будет unique_ptr <>.В этом случае вам следует придерживаться boost :: scoped_ptr.hpp, так как std :: auto_ptr скоро будет полностью устаревшим.

#include <boost/scoped_ptr.hpp>
#include <iostream>

class Foo {
  Foo() { std::cout << "constructor" << std::endl; }
public:
  static Foo& Get() {
    static boost::scoped_ptr<Foo> ptr(new Foo);
    return *ptr;
  }
  ~Foo() { std::cout << "destructor" << std::endl; }
};

int main() {
  Foo& f = Foo::Get();
  // f is now valid until the program exits. 
  //Then it is destroyed before the program finishes exiting.
  std::cout << "Work here" << std::endl;
}

Или вы можете написать собственный упрощенный scoped_ptr.

template<typename _T>
class scoped_ptr {
    _T* const mPtr;
  public:
    scoped_ptr(_T* const t) : mPtr(t) {}
    ~scoped_ptr() { delete mPtr; }
    operator _T* () { return const_cast<_T*>(mPtr); }
    // add more operators like -> if you want them
};
...