4

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

  • I can have a class Application which is abstract
  • And there can be multiple classes derived from Application, for example SampleApplication and OtherSampleApplication.
  • But I only want one instance of Application, whatever the actual type is, so I've been trying to make it a singleton.

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?

12
  • One possible approach is to use multiple inheritance (2) and explicitly specify preferable method in SampleApplication class: public: using Singleton<SampleApplication>::GetInstance; Commented Sep 4, 2021 at 15:06
  • Application abstract has the GetInstance, or alternatively Singleton<Application>, and main passes into the that the derived Application instance. If the singleton has already been initialized with an instance, it disallows the second re-initialization (throw an exception? return error code? silently ignore?). Commented Sep 4, 2021 at 15:11
  • How are you planning to use this if you make it work? I'm not getting a clear picture of where you are planning to let the dynamic dispatch to come in, since you are storing a plain T in your singleton. Commented Sep 4, 2021 at 15:12
  • There can be only one Application object, and that object is a sub-object of a class derived from Application. Therefore, there can be only one class derived from Application that gets instantiated when the program runs, right? Do you know at compile time which derived class this is? Commented Sep 4, 2021 at 15:18
  • @dewaffled Doing so and adding another OtherSampleApplication class, you can have things like: SampleApplication& application = SampleApplication::GetInstance(); OtherSampleApplication& otherApplication = OtherSampleApplication::GetInstance(); Commented Sep 4, 2021 at 15:25

3 Answers 3

1

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)

Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.