Daniel Marques
Daniel Marques

Reputation: 526

Singleton with multiple inheritance

I've been trying to find a singleton implementation where:

Here's my current Singleton implementation:

template<class T>
class Singleton
{
public:
    static T& GetInstance()
    {
      static T instance;
      return instance;
    }

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

protected:
    Singleton() = default;
    virtual ~Singleton() = default;
};

Then, for the derived classes, I've tried a number of things:

  1. class Application : public Singleton<Application> with class SampleApplication : public Application
    This will not work because we're trying to create an Application instance instead of a SampleApplication.
  2. class Application : public Singleton<Application> with class SampleApplication : public Application, public Singleton<SampleApplication>
    This doesn't compile because the GetInstance() is ambiguous. I tried to work around this with virtual inheritance but couldn't get it to compile.
  3. class Application with class SampleApplication : public Application, public Singleton<SampleApplication>
    Doesn't meet the "requirements" I listed above because we can have another singleton of another derived class.

Does anyone know a way to accomplish this?

Upvotes: 4

Views: 793

Answers (3)

JaMiT
JaMiT

Reputation: 17082

This is an approach that will produce compile-time errors if there is more than one class derived from Application. Usually. It is not fool-proof, and it does have drawbacks. I think it is worth considering.
Given the class name "Application", I guess that this is potentially part of a library. If the library is not header-only, there is an extra consideration that I'll address that at the end.

The overall picture for this approach is simple inheritance, plus an extra step for whomever defines a class derived from Application.

class Application       : public Singleton<Application> { /* ... */ };
class SampleApplication : public Application            { /* ... */ };
REGISTER_APPLICATION(SampleApplication)     // Must not be in a header

The last line will be explained later.

First, let's hide Singleton<Application>::GetInstance(), which will not compile since Application is abstract. (I am assuming that everyone will type the shorter Application::GetInstance() to use this function; if not, this approach is scrap.) Add the function declaration to the abstract class, but do not add a definition yet.

class Application : public Singleton<Application> {
  public:
    static Application & GetInstance();

    // ...
};

The definition will be added elsewhere, not in a header file. Taking the definition out of header files is the reason for hiding the function in the Singleton template. (While the extern keyword allows moving some template definitions out of header files, my understanding is that it does not fully suppress an inline definition when there is one, as in this case.) Where will the definition of GetInstance() live? That's where the extra step comes in. It's also where I could get blasted because Macros Are Evil unless they are needed. It's needed. Put the definition of this function in a macro.

#define REGISTER_APPLICATION(name)       \
Application& Application::GetInstance()  \
{                                        \
    static name instance;                \
    return instance;                     \
}

Now document that when deriving from Application, this macro must be used in a source file, not a header file. The parameter to the macro is the name of the derived class; if the derived class is SampleApplication then the line to add to the source file is REGISTER_APPLICATION(SampleApplication).


So what happens when someone messes up?

If the macro is not used, the linker will complain about the undefined reference to Application::GetInstance(). Unfortunately, this is not as clear about the problem as I would like. Probably put an entry in your framework's FAQ covering this.

If the macro is used twice, the linker will complain about multiple definitions of Application::GetInstance(). This is why the definition cannot be given inline. We want the one definition rule to apply. This is not fool-proof (maybe there are two classes derived from Application and only one remembered the macro), but it might be adequate.


For a library where parts of Application are defined outside headers, there would be a problem in that Application::GetInstance() cannot be part of the library, leading to an undefined reference when compiling the library. One solution for that is to make Application a wrapper for another class that is supposed to be internal to the library. For example:

class Application : public InternalApplication, public Singleton<Application> {
    static Application & GetInstance();
    // Everything else is inherited from InternalApplication.
};

Now Application can be header-only, while InternalApplication can have components in a static (or dynamic) library.

Upvotes: 1

Pepijn Kramer
Pepijn Kramer

Reputation: 13120

This solution shows how only allowing one instance based on a baseclass can work:

#include <iostream>
#include <type_traits>

//---------------------------------------------------------------------------------------------
// type for checking if something is a singleton later
struct SingletonBase 
{
};

//---------------------------------------------------------------------------------------------
// specialized singleton to be used in baseclass (Application)
// keeps track of whether an instance of the baseclass has already been made
template<typename T>
struct Singleton : 
    public SingletonBase
{
    Singleton() 
    {
        has_instance = true;
    }

    static bool has_instance;
};

// has_instance initilization
template<typename T>
bool Singleton<T>::has_instance = false;

//---------------------------------------------------------------------------------------------
// Baseclass for applications.

class Application :
    public Singleton<Application>
{
protected:
    Application() = default; // do not allow client code to make instance of base class
};

class SomeOtherKindOfApplication :
    public Singleton<SomeOtherKindOfApplication>
{
protected:
    SomeOtherKindOfApplication() = default;
};

//---------------------------------------------------------------------------------------------
// application types derived from Application and SomeOtherApplication 

class Application1 :
    public Application
{
public:
    void Hello() 
    {
        std::cout << "Hello, " << std::endl;
    }
};

class Application2 :
    public Application
{
public:
    void World()
    {
        std::cout << "World !" << std::endl;
    }
};

class SomeOtherKindOfApplication1 :
    public SomeOtherKindOfApplication
{
public:
    void Bye()
    {
        std::cout << "Bye!" << std::endl;
    }
};

class NotASingleton
{
};

//---------------------------------------------------------------------------------------------
// Singleton instance getter

template<typename T>
static T& GetInstance()
{
    static_assert(std::is_base_of_v<SingletonBase, T>, "Can only create instances of classes derived from singleton");
    if (T::has_instance)
    {
        throw std::runtime_error("An application instance has already been made");
    }

    static T instance;
    return instance;
}

//---------------------------------------------------------------------------------------------

int main()
{
    auto instance = GetInstance<Application1>();
    instance.Hello();

    try
    {
        // getting an instance of another application derived
        // from Application will fail (runtime)
        auto instance2 = GetInstance<Application2>();
        instance2.World();
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << std::endl;
    }

    // but getting application with another baseclass should be fine
    auto instance3 = GetInstance<SomeOtherKindOfApplication1>();
    instance3.Bye();

    // next line will not even compile, NotASingleton isn't a singleton
    // auto no_instance = GetInstance<NotASingleton>();
}

Upvotes: 1

Sam Varshavchik
Sam Varshavchik

Reputation: 118445

Since you mentioned that you will settle for a runtime error:

template<class T>
class Singleton : private ReallySingleton {

The rest of the singleton is as is. The private base class will go something like this:


class ReallySingleton {

   static int counter=0;

protected:
   ReallySingleton()
   {
      if (++counter > 1)
        throw std::runtime_error{"Too many singletons"};
   }
};

(ReallySingleton::counter will need to be defined somewhere where that's convenient)

Upvotes: 1

Related Questions