C++: Реализация Singleton на базе shared_ptr

Шаблон одиночка (или Pattern Singleton) - достаточно известный шаблон программирования, но стандартная реализация имеет ряд недостатков, которые не позволяют в полной мере использовать ее в unit-тестировании. Сам я не сторонник использования одиночки, тем не менее этот шаблон был мной использован в нескольких проектах. Однако моя реализация отличается от стандартной.

Прежде чем приступать к работе, я стараюсь определить цели, конечный результат, которого я хочу добиться. В данном случае я хотел получить класс, который удовлетворял следующим требованиям:
  • получить экземпляр класса просто обращаясь к статическому методу
  • необходимо контролировать время жизни объекта ( это нужно не столько в приложении, сколько в unit-тестировании. )
  • Возможность использования наследуемого класса
  • Возможность передачи параметров конструктору
  • Потоко-безопасность ( если необходимо )

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

#include <mutex>
#include <memory>

template <
  typename Type_,
  typename Mutex_ = std::mutex
>
class Singleton {
 public:
 typedef Mutex_ Mutex;
 typedef std::shared_ptr<Type_> share_ptr;
 typedef std::weak_ptr<Type_> weak_ptr;
 typedef std::lock_guard<Mutex_> lock_guard;

 public:
 static bool set( share_ptr value ){
   lock_guard lock( mutex() );
   if ( ptr().expired() ) {
     ptr() = value;
     return true;
   }
   return false;
 }

 static share_ptr get(){
   lock_guard lock(mutex());
   return ptr().lock();
 }

 private:
   Singleton() = delete;
   Singleton( const Singleton& ) = delete;

   static Mutex& mutex() {
     static Mutex m;
     return m;
   };

   static weak_ptr& ptr() {
     static weak_ptr p;
     return p;
   };
};

Вместо того чтобы описывать суть реазиции, начнем с примеров использования.

#include <stdlib.h>
#include <iostream>
// Один экземпляр класса в приложении
class Foo {
public:
  Foo():value(10){};
  int value;
};

// Определяем тип для нашего одиночки
typedef Singleton<Foo> FooInst;

void print_foo();

int main ( int argc, char *argv[] ){
 // Создание экземпляра
 // И держим до тех пор пока он нужен
 // то есть до конца main
  FooInst::share_ptr foo( new Foo );
  FooInst::set( foo );

  print_foo();

  return EXIT_SUCCESS;
}

void print_foo();
{
  using namespace std;
  // получаем указатель через shared_ptr
  auto foo = FooInst::get();
  if ( foo ) {
    cout << "Foo.value is " << foo->value << endl;
  } else {
    cout << "Foo is not instanced" << endl;
  }
};

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

struct null_mutex {
  void lock() {}
  void unlock() noexcept {}
  bool try_lock() { return true; }
};

В boost библиотеке это уже сделано: boost::interprocess::null_mutex. В нашем примере в этом счучае прменяется только одна строчка ( если не брать в расчет определение пустого макроса ).

typedef Singleton<Foo, null_mutex> FooInst;

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

Ссылки по теме: