Шаблон проектирования C++ Singleton

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

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

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

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

18 ответов


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

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

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

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

см. эту две статьи о порядке инициализации и о том, как справиться:
порядок инициализации статических переменных
Поиск проблем статического порядка инициализации C++

см. эту статью, описывающую продолжительность жизни:
каково время жизни статической переменной в функции C++?

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

см. эту статью, в которой объясняется, почему двойная блокировка не будет работать на C++:
каковы все общие неопределенные поведения, о которых должен знать программист на C++?
Dr Dobbs: C++ и опасности двойной проверки блокировки: Часть I


будучи Синглтоном, вы обычно не хотите, чтобы он был уничтожен.

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


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

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

class Singleton
{
private:
   Singleton();

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

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


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

singleton<C>()

используя

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

ни этот, ни ответ Кэтэлина не являются автоматически потокобезопасными в текущем C++, но будут в C++0x.


@Loki Astari ответ отлично.

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

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

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() {}
};

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

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

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

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

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


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

#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-это расположение памяти одноэлементного объекта, одинаковое для продолжительности программы, но (обычно!) отличается каждый раз, когда программа запускается.

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


Он действительно, вероятно, выделен из кучи, но без источников нет способа узнать.

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

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

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

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

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


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


Я не нашел реализацию 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>


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

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


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

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

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 потому что эта информация предоставляется во время компиляции. Вам также не нужно беспокоиться о the Storeвремя жизни, потому что компилятор требует, чтобы он был глобальным. Если действительно существует только один экземпляр Store тогда в этом подходе нет накладных расходов; с более чем одним экземпляром компилятор должен быть умным в генерации кода. При необходимости ItemRef класс можно даже сделать friend of Store (вы можете иметь templated друзей!).

если 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 экземпляр, и всегда доступ к магазинам через их экземпляр оболочки (таким образом, забывая о кровавых деталях параметров шаблона, необходимых для использования Store).


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

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

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


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


Как насчет использования размещения нового, как это:

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