C ++ Singleton дизайн шаблона

532

Недавно я столкнулся с реализацией/реализацией шаблона проектирования Singleton для С++. Это выглядело так (я принял это из примера реальной жизни):

// a lot of methods are omitted here
class Singleton
{
   public:
       static Singleton* getInstance( );
       ~Singleton( );
   private:
       Singleton( );
       static Singleton* instance;
};

Из этого объявления я могу вывести, что поле экземпляра инициируется в куче. Это означает, что есть выделение памяти. Для меня совершенно непонятно, когда именно память будет освобождена? Или есть ошибка и утечка памяти? Кажется, что в реализации есть проблема.

Мой главный вопрос: как мне его реализовать правильно?

  • 0
    Что в деструкторе?
  • 0
    освободить некоторые ресурсы, но, похоже, этого не произойдет.
Показать ещё 13 комментариев
Теги:
design-patterns
singleton

19 ответов

904
Лучший ответ

В 2008 году я представил реализацию c++ 98 шаблона проектирования Singleton, который лениво оценивается, гарантируется уничтожением, а не технически-поточно-безопасным:
Может ли кто-нибудь предоставить мне образец Singleton в c++?

Ниже приведена обновленная версия c++ 11 шаблона проектирования Singleton, который оценивается с леними, правильно уничтожен и потокобезопасен.

class S
{
    public:
        static S& getInstance()
        {
            static S    instance; // Guaranteed to be destroyed.
                                  // Instantiated on first use.
            return instance;
        }
    private:
        S() {}                    // Constructor? (the {} brackets) are needed here.

        // C++ 03
        // ========
        // Don't forget to declare these two. You want to make sure they
        // are unacceptable otherwise you may accidentally get copies of
        // your singleton appearing.
        S(S const&);              // Don't Implement
        void operator=(S const&); // Don't implement

        // C++ 11
        // =======
        // We can use the better technique of deleting the methods
        // we don't want.
    public:
        S(S const&)               = delete;
        void operator=(S const&)  = delete;

        // Note: Scott Meyers mentions in his Effective Modern
        //       C++ book, that deleted functions should generally
        //       be public as it results in better error messages
        //       due to the compilers behavior to check accessibility
        //       before deleted status
};

См. Эту статью о том, когда использовать синглтон: (не часто)
Синглтон: как его использовать

См. Две статьи о порядке инициализации и о том, как справиться:
Порядок инициализации статических переменных
Поиск статических задач инициализации c++

См. Эту статью, описывающую сроки жизни:
Каково время жизни статической переменной в функции c++?

См. Эту статью, в которой обсуждаются некоторые потоковые последствия для синглетонов:
Экземпляр Singleton, объявленный как статическая переменная метода GetInstance, является поточно-безопасным?

См. Эту статью, в которой объясняется, почему двойная проверка блокировки не будет работать на c++:
Каковы все распространенные неопределенные типы поведения, о которых должен знать программист c++?
Д-р Доббс: c++ и "Опасности блокировки с двойной проверкой": часть I

  • 1
    @fnieto: Спасибо. То, что я пытался подразумевать с помощью конструктора S (), должно быть объявлено закрытым. Поскольку синглтоны будут иметь других членов (иначе зачем сингелтон), которые нужно инициализировать (обратите внимание, что это не реализация Не), поэтому, когда вы объявляете конструктор, он должен быть закрытым.
  • 18
    Хороший ответ. Но следует заметить, что это не потокобезопасный stackoverflow.com/questions/1661529/…
Показать ещё 64 комментария
36

Будучи Singleton, вы обычно не хотите, чтобы он был разрушен.

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

  • 0
    и я как раз собирался нажать "Пост", так как ваш, почти идентичный моему, ответ появился :)
  • 3
    если delete никогда не вызывается явно для статического экземпляра Singleton *, разве это технически не будет считаться утечкой памяти?
Показать ещё 14 комментариев
27

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

Я предпочитаю такую ​​реализацию (на самом деле, я не правильно сказал, что предпочитаю, потому что я избегаю синглетонов как можно больше):

class Singleton
{
private:
   Singleton();

public:
   static Singleton& instance()
   {
      static Singleton INSTANCE;
      return INSTANCE;
   }
};

У него нет динамического распределения памяти.

  • 2
    В некоторых случаях эта ленивая инициализация не является идеальным шаблоном для подражания. Один пример - если конструктор синглтона выделяет память из кучи, и вы хотите, чтобы это распределение было предсказуемым, например, во встроенной системе или в другой жестко контролируемой среде. Когда я предпочитаю использовать шаблон Singleton, я предпочитаю создавать экземпляр как статический член класса.
  • 3
    Для многих более крупных программ, особенно с динамическими библиотеками. Любой глобальный или статический объект, который не является примитивным, может привести к ошибкам / сбоям при выходе из программы на многих платформах из-за проблем разрушения при выгрузке библиотек. Это одна из причин, по которой многие соглашения о кодировании (включая Google) запрещают использование нетривиальных статических и глобальных объектов.
Показать ещё 1 комментарий
10

Другая альтернатива без выделения: создайте одноэлементный, скажем, класс C, как вам это нужно:

singleton<C>()

используя

template <class X>
X& singleton()
{
    static X x;
    return x;
}

Ни тот, ни ответ Cătălin автоматически не потокобезопасны в текущем С++, но будут в С++ 0x.

  • 0
    В настоящее время под gcc это потокобезопасно (и было некоторое время).
  • 8
    Проблема с этим дизайном заключается в том, что при использовании нескольких библиотек. Каждая библиотека имеет свою собственную копию сингтона, которую использует эта библиотека. Так что это уже не синглтон.
8

@Локи Астари ответ отлично.

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

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

class Singleton
{
public:
    Singleton(Singleton const&) = delete;
    Singleton& operator=(Singleton const&) = delete;

    static std::shared_ptr<Singleton> instance()
    {
        static std::shared_ptr<Singleton> s{new Singleton};
        return s;
    }

private:
    Singleton() {}
};
5

Если вы хотите выделить объект в кучу, почему бы не использовать уникальный указатель. Память также будет освобождена, так как мы используем уникальный указатель.

class S
{
    public:
        static S& getInstance()
        {
            if( m_s.get() == 0 )
            {
              m_s.reset( new S() );
            }
            return *m_s;
        }

    private:
        static std::unique_ptr<S> m_s;

        S();
        S(S const&);            // Don't Implement
        void operator=(S const&); // Don't implement
};

std::unique_ptr<S> S::m_s(0);
  • 3
    Устаревший в C ++ 11. Вместо этого рекомендуется unique_ptr. cplusplus.com/reference/memory/auto_ptr cplusplus.com/reference/memory/unique_ptr
  • 2
    Это не потокобезопасно. Лучше сделать m_s локальной static m_s getInstance() и немедленно инициализировать ее без теста.
4

Вот простая реализация.

#include <Windows.h>
#include <iostream>

using namespace std;


class SingletonClass {

public:
    static SingletonClass* getInstance() {

    return (!m_instanceSingleton) ?
        m_instanceSingleton = new SingletonClass : 
        m_instanceSingleton;
    }

private:
    // private constructor and destructor
    SingletonClass() { cout << "SingletonClass instance created!\n"; }
    ~SingletonClass() {}

    // private copy constructor and assignment operator
    SingletonClass(const SingletonClass&);
    SingletonClass& operator=(const SingletonClass&);

    static SingletonClass *m_instanceSingleton;
};

SingletonClass* SingletonClass::m_instanceSingleton = nullptr;



int main(int argc, const char * argv[]) {

    SingletonClass *singleton;
    singleton = singleton->getInstance();
    cout << singleton << endl;

    // Another object gets the reference of the first object!
    SingletonClass *anotherSingleton;
    anotherSingleton = anotherSingleton->getInstance();
    cout << anotherSingleton << endl;

    Sleep(5000);

    return 0;
}

Создается только один объект, и эта ссылка на объект возвращается каждый раз.

SingletonClass instance created!
00915CB8
00915CB8

Здесь 00915CB8 - это ячейка памяти singleton Object, то же самое в течение всей программы, но (обычно!) различна при каждом запуске программы.

N.B. Это не безопасный поток. Вы должны обеспечить безопасность потока.

4

Решение в принятом ответе имеет существенный недостаток - деструктор для singleton вызывается после того, как элемент управления покидает "главную" функцию. На самом деле могут возникнуть проблемы, когда некоторые зависимые объекты выделяются внутри "main".

Я встретил эту проблему, пытаясь ввести Singleton в приложение Qt. Я решил, что все мои диалоги настройки должны быть Singletons, и приняли шаблон выше. К сожалению, основной класс Qt "QApplication" был выделен в стеке в "основной" функции, а Qt запрещает создавать/уничтожать диалоги, когда объект приложения недоступен.

Вот почему я предпочитаю выделенные в кучу синглтоны. Я предоставляю явные методы "init()" и "term()" для всех синглтонов и называю их внутри "main". Таким образом, у меня есть полный контроль над порядком создания/уничтожения одиночек, а также я гарантирую, что будут созданы синглтоны, независимо от того, кто-то назвал "getInstance()" или нет.

  • 1
    Похоже, вы пытались использовать это неправильно.
  • 2
    Если вы ссылаетесь на принятый в настоящее время ответ, ваше первое утверждение неверно. Деструктор не вызывается до тех пор, пока все статические объекты продолжительности хранения не будут уничтожены.
2

На самом деле это, вероятно, выделено из кучи, но без источников нет возможности узнать.

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

Singleton * Singleton::getInstance() {
    if (!instance) {
        instance = new Singleton();
    };
    return instance;
};

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

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

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

  • 0
    вы можете сделать вывод, поскольку вы можете видеть, что переменная экземпляра является указателем на экземпляр класса.
  • 3
    Нет необходимости динамически размещать синглтон. На самом деле это плохая идея, так как нет способа автоматически отменить выделение ресурсов, используя вышеуказанный дизайн. Пусть он выпадает из области видимости - это не называет деструкторами и просто ленится.
Показать ещё 1 комментарий
1

Кто-нибудь упомянул std::call_once и std::once_flag? Большинство других подходов, включая двойную проверку блокировки, нарушены.

Одной из основных проблем в реализации одноэлементного шаблона является безопасная инициализация. Единственный безопасный способ - защитить последовательность инициализации синхронизирующими барьерами. Но сами эти барьеры должны быть безопасно начаты. std::once_flag - это механизм, гарантирующий безопасную инициализацию.

1

Я не нашел CRTP-реализацию среди ответов, так вот вот:

template<typename HeirT>
class Singleton
{
public:
    Singleton() = delete;

    Singleton(const Singleton &) = delete;

    Singleton &operator=(const Singleton &) = delete;

    static HeirT &instance()
    {
        static HeirT instance;
        return instance;
    }
};

Чтобы просто наследовать свой класс, например: class Test : public Singleton<Test>

1

Речь идет об управлении жизненным временем объекта. Предположим, что у вас в вашем программном обеспечении больше, чем однопользовательских. И они зависят от Singleger Logger. Во время уничтожения приложения предположим, что другой одноэлементный объект использует Logger для регистрации шагов его уничтожения. Вы должны гарантировать, что Logger должен быть очищен последним. Поэтому, пожалуйста, также ознакомьтесь с этой статьей: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf

0

Простой одноэлементный класс, это должен быть ваш файл класса заголовка

#ifndef SC_SINGLETON_CLASS_H
#define SC_SINGLETON_CLASS_H

class SingletonClass
{
    public:
        static SingletonClass* Instance()
        {
           static SingletonClass* instance = new SingletonClass();
           return instance;
        }

        void Relocate(int X, int Y, int Z);

    private:
        SingletonClass();
        ~SingletonClass();
};

#define sSingletonClass SingletonClass::Instance()

#endif

Получите доступ к вашему синглтону следующим образом:

sSingletonClass->Relocate(1, 2, 5);
0

Здесь много ответов, и это было задано некоторое время назад, но я хотел бы добавить следующее. Благодарим Алана и Пола Эзуста. (Вступление к разработке шаблонов на С++ с Qt)

Шаблон Singleton является специализированным factory, используемым в ситуациях, когда вы хотите ограничить количество или тип созданных экземпляров. Метод CustomerFactory:: instance(), определенный ниже, является примером o singleton factory. Он создает объект, если это необходимо, но только в первый раз, когда этот метод вызывается. При последующих вызовах он всегда возвращает указатель на тот же объект.

CustomerFactory* CustomerFactory::instance(){
   static CustomerFactory* retval = 0;
   if (retval == 0) retval = new CustomerFactory(qApp);          1
   return retval;
}

1 Обеспечивает очистку этого объекта и всех его дочерних элементов при выходе из QApplication.

При работе с объектами кучи важно не оставлять утечки памяти. Вы можете использовать отношения родитель-ребенок QObjects, чтобы помочь в этом отношении.

0

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

struct Store{
   std::array<Something, 1024> data;
   size_t get(size_t idx){ /* ... */ }
   void incr_ref(size_t idx){ /* ... */}
   void decr_ref(size_t idx){ /* ... */}
};

template<Store* store_p>
struct ItemRef{
   size_t idx;
   auto get(){ return store_p->get(idx); };
   ItemRef() { store_p->incr_ref(idx); };
   ~ItemRef() { store_p->decr_ref(idx); };
};

Store store1_g;
Store store2_g; // we don't restrict the number of global Store instances

Теперь где-то внутри функции (например, main) вы можете сделать:

auto ref1_a = ItemRef<&store1_g>(101);
auto ref2_a = ItemRef<&store2_g>(201); 

Ссылкам refs не нужно сохранять указатель на соответствующий Store, потому что эта информация предоставляется во время компиляции. Вам также не нужно беспокоиться о времени жизни Store, потому что компилятор требует, чтобы он был глобальным. Если действительно существует только один экземпляр Store, то в этом подходе нет накладных расходов; с более чем одним экземпляром, до компилятора, чтобы быть умным в генерации кода. Если необходимо, класс ItemRef может быть даже сделан friend из Store (у вас могут быть шаблонные друзья!).

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

template <typename Store_t, Store_t* store_p>
struct StoreWrapper{ /* stuff to access store_p, e.g. methods returning 
                       instances of ItemRef<Store_t, store_p>. */ };

Теперь пользователь может создать тип StoreWrapper (и глобальный экземпляр) для каждого глобального экземпляра Store и всегда обращаться к хранилищам через свой экземпляр оболочки (таким образом забывая о деталях gory параметров шаблона, необходимых для использования Store).

0
#define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;}

Пример:

   class CCtrl
    {
    private:
        CCtrl(void);
        virtual ~CCtrl(void);

    public:
        INS(CCtrl);
-1

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

  • 2
    К сожалению нет. Это было подробно обсуждено некоторыми из лучших разработчиков C ++. Двойная проверка блокировки сломана в C ++ 03.
-2

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

-4

Как насчет использования нового размещения следующим образом:

class singleton
{
    static singleton *s;
    static unsigned char *buffer[sizeof(singleton)/4 *4] //4 byte align
    static singleton* getinstance()
    {
        if (s == null)
        {
            s = new(buffer) singleton;
        }
        return s;
    }
};
  • 4
    Зачем. Это кажется сложным. И деструктор никогда не называется. Также вам нужно исправить выражение для расчета размера, в настоящее время оно будет работать в среднем 25% времени (попробуйте ситуацию, когда sizeof (singelton) == 3. Тогда приведенное выше выражение приводит к массиву 0 размера.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню