Singleton и абстрактный базовый класс в С++

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

class IFoo {...}; // it ABC
class Foo : public IFoo {...};

мы имеем одноэлементный класс, определяемый следующим образом:

template <typename T>
class Singleton
{
public:
static T* Instance() {
   if (m_instance == NULL) {
      m_instance = new T();
   }
   return m_instance;
}
private:
static T* m_instance;
};

Итак, если я хочу использовать, как показано ниже: IFoo::Instance()->foo(); что мне делать?

Если я это сделаю: class IFoo : public Singleton<IFoo> {...}; он не будет работать, поскольку Singleton будет вызывать IFoo ctor, но IFoo - это ABC, поэтому его нельзя создать.

И это: class Foo : public IFoo, public Singleton<Foo> {...}; тоже не может работать, потому что в этом случае класс IFoo не имеет интерфейса для метода Instance(), поэтому вызов IFoo::Instance() завершится с ошибкой.

Любые идеи?

Ответ 1

Вы хотите использовать что-то вроде

IFoo my_foo = Singleton<Foo>::Instance();
my_foo->foo();

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

Ответ 2

Вы не можете этого сделать. IFoo - это интерфейс, по дизайну и определению. Поэтому число экземпляров равно 0. С другой стороны, определение одноэлементного класса состоит в том, что у вас есть 1 экземпляр. 0!= 1.

Ответ 3

Вы всегда можете сделать что-то вроде этого:

class IFoo {};
class Foo : public IFoo {};

template <typename T>
class Singleton
{
    // ..
};

typedef Singleton<Foo> FooSingleton;

int main()
{
    FooSingleton::Instance()->foo();

    return 0;
}

Ответ 4

Досадный мета-ответ: "Почему вы используете синглтон?" Мне еще предстоит найти ситуацию, когда вам действительно нужно ее использовать. ИМХО его недостатки перевешивают его преимущества, в реальных жизненных ситуациях.

Использование чего-то типа "boost:: noncopyable" может быть тем, что вам нужно.

См. это сообщение для дополнительной информации

Ответ 5

Вот еще одно возможное решение, которое я нашел, что работает хорошо.

Добавьте это в Singleton:

#ifndef ABSTRACT_CLASS
    static T* D()
    {
        return new T();
    }
#else
    static T* D()
    {
        return NULL;
    }
#endif

static T* Instance( T*(*func)() )
{
    if( !m_instance )
    {
        m_instance = func();
    }

    return m_instance;
}

static T* Instance()
{
    if( !m_instance )
    {
        m_instance = D();
    }

    return m_instance;
}

Убедитесь, что абстрактный класс находится в заголовке, а реализации - в источниках.

Например:

// IFoo.h
//
#define ABSTRACT_CLASS

class IFoo
{
    virtual ~IFoo() {}

    virtual void SomeFunc() = 0;
};

extern IFoo* BuildFoo();


// Foo.cpp
//
#include "IFoo.h"

class Foo : public IFoo
{
    Foo() {}
    ~Foo() {}

    void SomeFunc() {}
};

IFoo* BuildFoo() { return new Foo(); }

С помощью этих дополнений вы можете сделать следующее:

IFoo::Instance( BuildFoo );

IFoo::Instance()->SomeFunc();

Просто запомните #define ABSTRACT_CLASS в заголовке для каждого абстрактного класса.

Ответ 6

Посмотрите на это так: в вашей программе нет ничего, что могло бы сказать компилятору, какую реализацию интерфейса IFoo он должен создавать. Помните, что помимо Foo могут быть другие реализации.

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

Ответ 7

Мне пришлось сделать что-то подобное, чтобы добавить модульные тесты к некоторому устаревшему коду. Мне пришлось заменить существующий синглтон, который использовал шаблон. Я дал два параметра шаблону singleton, первый - интерфейс, второй - реализация.

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

template <typename IfaceT, typename ImplT>
class Singleton
{
public:
   static IfaceT* Instance() {
      if (m_instance == NULL) {
         m_instance = new ImplT();
      }
      return m_instance;
   }

   // Only used for unit tests 
   // Takes ownership of instance
   static void setTestInstance(IfaceT* instace) {
      m_instance = instance;
   }
private:
   static IfaceT * m_instance;
};

В этом случае setTestInstance должен использовать std::auto_ptr, а m_instance должен быть boost::scoped_ptr. Чтобы избежать утечек памяти.

Ответ 8

Я думаю, лучшим решением было бы ввести класс или метод factory. Представьте себе следующее:

struct FooCreator
{
  typedef IFoo*     result_type;

  result_type operator()()const
  {
     return new Foo;
  }
};

template<class Factory>
struct Singleton
{

  static typename Factory::result_type instance()
  {
    if(instance_==typename Factory::result_type())
      instance_ = Factory()();
    return instance_;
  } 

private:
  Singleton(){};

  static typename Factory::result_type instance_;
};

template<class F>
typename F::result_type Singleton<F>::instance_ = typename F::result_type();

С наилучшими пожеланиями,
Ованес

Ответ 9

Недавно я столкнулся с той же проблемой.

Он может быть реализован с тем, что я знаю как gem singleton. Он использует assert для форсирования уникальности и Любопытно повторяющийся шаблон шаблона для вызова реализации интерфейса через singleton:

template <typename T>
class Singleton {
 public:
  Singleton(const Singleton<T>&) = delete;
  Singleton& operator=(const Singleton<T>&) = delete;       
  Singleton() {
    assert(!msSingleton);
    msSingleton = static_cast<T*>(this);
  }
  ~Singleton(void) {
    assert(msSingleton);
    msSingleton = 0;
  }
  static T& getSingleton(void) {
    assert(msSingleton);
    return (*msSingleton);
  }
 protected:
  static T* msSingleton; 
};    

class IFoo : public Singleton<IFoo> {    
 public:
  virtual void foo() = 0;
};

class FooImpl : public IFoo {
 public:
  FooImpl();
  void foo() override { std::cout << "FooImpl::foo()\n"; }
};

template <>
IFoo* Singleton<IFoo>::msSingleton = 0;

FooImpl::FooImpl() { msSingleton = this; }

После создания экземпляра FooImpl после вызова IFoo::getSingleton().foo() вызов IFoo::getSingleton().foo() вызывает код FooImpl.

int main() {
  FooImpl f;
  IFoo::getSingleton().foo();
}

демонстрация