0

I need to define a singleton class in C++ that is implemented separately across different platforms. (e.g. macOS, Windows, Linux, etc.) In case it matters, this is for a Qt application and the singleton will be inheriting from QObject.

I could do this simply using some preprocessor directives and including totally separate implementations, like so:

// Singleton.h
#include <QtGlobal>

#if defined Q_OS_WIN
    #include "Singleton_win.h"
#elif defined Q_OS_MAC
    #include "Singleton_mac.h"
#elif ... etc ...
#endif

However, for my own planning and sanity I'd love it if there was a way I could define what actual methods need to be implemented across all platforms so that the different implementations can stay at least partially synchronized.

If I were programming in a language with interfaces, I would handle this just by defining an interface and having each platform-specific version of the singleton implement it.

However, this is C++, and the closest I have to an interface is an abstract base class. But using an abstract base class is troublesome. I need a static method on whatever the singleton is for getting the global instance of that singleton, and of course C++ doesn't allow abstract static methods. I can't use a templated static method that returns the right implementation of the singleton because everywhere else in my code that uses the singleton doesn't and shouldn't know which implementation it's going to use.

Lastly, I'd really like to avoid any patterns that involve an excessive amount of boilerplate code. One such pattern I feel could be used to create this Singleton but qualifies as involving excessive boilerplate is the PImpl pattern, because it would require me doing something like this:

// Singleton.h

class Singleton {
public:
    Singleton();
    ~Singleton();
    static Singleton * shared() { static Singleton s; return &s; };
    void publicMethodA();
    void publicMethodB();
    // etc.
protected:
    class impl;
    impl *p;
}

// Singleton.cpp

class Singleton::impl {
public:
    void publicMethodA();
    void publicMethodB();
    // etc.
}

void Singleton::impl::publicMethodA() {
    // actual publicMethodA implementation here
}

void Singleton::impl::publicMethodB() {
    // actual publicMethodB implementation here
}

// further public methods implemented here

// Singleton constructors and destructors implemented here

void Singleton::publicMethodA() {
    impl->publicMethodA();
}

void Singleton::publicMethodB() {
    impl->publicMethodB();
}

// further boilerplate for Singleton's public methods implemented here

Basically my gripe is that for every public method on Singleton that I write, I have to add a method that calls through to another method with an identical signature on Singleton::impl. This is just my personal preference, but I can't stand boilerplate code like that! I'd really like to avoid it.

Furthermore, once we consider that this is going to be a QObject-inherited class with Qt signals, slots, and possibly properties, having an Singleton::impl will introduce further complexities because it's Singleton that needs to be emitting signals and receiving connections from other QObjects.

So given that, are there any patterns I can use that allow me to implement this singleton the way I'm looking to do?

Bri Bri
  • 2,169
  • 3
  • 19
  • 44
  • 1
    Please don't use singletons (or any other global objects for that matter). They (usually) create way more problems than they solve. There's good reason why many people consider the "singleton pattern" to be an *anti*-pattern. In the code-base I work on we (unfortunately) have many singletons and they have caused a *lot* of pain over the years. We are actively trying to remove them, but unfortunately, singletons are much easier to introduce than remove once people start using them everywhere. – Jesper Juhl Apr 23 '19 at 19:58
  • 1
    Consider separate implementation files, one for each platform. For each platform, you compile/link with the matching source tile. No need for a middle-man class. – François Andrieux Apr 23 '19 at 19:58
  • @JesperJuhl I'm well aware of the pitfalls of singleton classes. However I'm implementing code that manages a globally accessible resource, it has to be an instance of a class in order to take advantage of Qt's mechanisms, and it doesn't make sense for there to be more than one instance of that class. A singleton makes sense here. – Bri Bri Apr 23 '19 at 20:03
  • @FrançoisAndrieux I've considered that but each platform-specific implementation is going to require a different set of private methods and member variables, some of which may be platform specific, so I can't have there be a single header file that they all share unless I load it up with tons of precompiler directives, which itself makes for some ugly code that's harder to maintain. – Bri Bri Apr 23 '19 at 20:05
  • @GuyGizmo You can meet both solution half way by forward declaring a private class type and having a `std::unique_ptr` data member to it. Then, define that type in each platform's own source file. That way, you essentially "add private members" to your type without touching the public header at all. – François Andrieux Apr 23 '19 at 20:06
  • @FrançoisAndrieux I believe you're referring to the PImpl pattern, which I've already addressed in my question. I'd be willing to do that if there were a good way to avoid the boilerplate code that I think it requires. – Bri Bri Apr 23 '19 at 20:09
  • @GuyGizmo It's similar, but you don't need to put all the members in your implementation type. You can leave the common interface and members in the user facing type. If the private portions are too strongly coupled with the public portions, then you don't have much choice other than going full PImpl. – François Andrieux Apr 23 '19 at 20:12
  • [This post](https://stackoverflow.com/questions/3313754/static-abstract-methods-in-c) has an answer describing how to do this with a factory. Otherwise Francois's suggestion to use encapsulation rather than inheritance also sounds like a reasonable solution. – ChrisD Apr 23 '19 at 20:21
  • @ChrisD I did read that post but I'm not sure how to adapt a factory pattern like that to a singleton class. If you can elaborate on that though that would be much appreciated! – Bri Bri Apr 23 '19 at 20:24
  • do not use singletons ! just have a Interface, then provide a implementation for each platform – skeller Apr 23 '19 at 20:29
  • As you would for the singleton. Either a static `Factory::TInterface*` member that's initialized on the first call to `Create` or just a static `Thingy*` member on the class itself that's initialized on the first call to `CreateInternal`. – ChrisD Apr 23 '19 at 20:35
  • This specific problem is solvable through Abstract Factory better in my opinion. In this case, all C++ implementations for each platform would be concrete factories. First, you just introduce some public API which is going to work in a cross-platform manner both on Windows and MAC. Then, you provide C++ implementations for each platform (header can be made to be the same for all platforms through usage of PIMPL idiom). And finally, you include your header where needed, and instantiate it like a normal class in any way you need it to be instantiated. Singleton is not necessary in this scenario. – Vaidotas Strazdas Apr 30 '19 at 10:01
  • If any of you would like to submit any of these suggestions as an answer, I can try them out and if it works mark it as correct. Or at the very least we can continue the discussion there. As things stand though no one has submitted anything other than comments so there's no way for me to mark this as correct! – Bri Bri Apr 30 '19 at 16:01

0 Answers0