3

I work in a project where Singletons are usually implemented like this:

class Singleton
{
public:
    static Singleton& get();
    virtual ~Singleton() = default;

    // Used for unit tests
    static void reset();

protected:
    static std::unique_ptr<Singleton>& instance();
};
unique_ptr<Singleton>& Singleton::instance()
{
    static unique_ptr<Singleton> instance;
    return instance;
}

Singleton& Singleton::get()
{
    auto& instance = instance();
    if (!instance) {
        // We cannot use make_unique<T>() as constructor is private
        instance = unique_ptr<Singleton>(new Singleton());
    }
    return *instance;
}

void Singleton::reset() { instance().reset(); }
// Private constructor
Singleton::Singleton() {}

No thread safety is required here.
Is there any advantages of using a static unique_ptr ?
What are the consequences of creating the Singleton with unique_ptr<T>(new T()) ?

Since our Singletons can carry (some) global state, a public reset() was implemented for testing purposes, is it the only way and can this be improved ?

I have found some examples of C++ singleton design patterns here. But never implemented with unique_ptr like mine.

  • 7
    What is gain of having `std::unique_ptr& instance();` instead: `Singleton& instance();`? Are you planing to allow external code to destroy instance of `Singelton`? – Marek R Jan 26 '23 at 16:56
  • If you are going to use `unique_ptr`, why not declare and initialize it in `get()` and get rid of `instance()`? `Singleton& Singleton::get() { static unique_ptr instance(new Singleton()); return *instance; }` For that matter, why use a `unique_ptr` at all? Do you really need to `reset()` a singleton? The typical (and thread-safe) implementation is to just use a `static` object, eg: `Singleton& Singleton::get() { static Singleton instance; return instance; }` – Remy Lebeau Jan 26 '23 at 17:09
  • I can't think of any benefit of `unique_ptr` here. All this implementation has done is reduce the thread safety. `Singleton& Singleton::get() { static Singleton instance; return instance; }` is more thread safe and has the same result. Thread safety may not be important now, but it doesn't hurt either. – Dean Johnson Jan 26 '23 at 17:12
  • Thank you for your answers, when a `create()` and `get()` is needed (e.g creation with parameters) `instance()` is just a shortcut. I apologize, it's not very useful in my example. – Moulagaufres Jan 26 '23 at 17:29
  • 2
    There is no need to use a `unique_ptr` for a singleton. Use a [Meyers' Singleton](https://stackoverflow.com/questions/17712001/how-is-meyers-implementation-of-a-singleton-actually-a-singleton) instead – NathanOliver Jan 26 '23 at 17:37
  • How should I deal with the `reset()` function with a "Meyers' Singleton" static reference ? The tricks here is that you can destroy the singleton and create a new one. – Moulagaufres Jan 26 '23 at 17:55
  • @Moulagaufres if you *always* create new one after destroy, then yes you can do it with just a reference. – apple apple Jan 26 '23 at 17:58
  • *How should I deal with the reset()?* -- If you have non-const reference or pointer you can always call destructor and in-place new. – sklott Jan 26 '23 at 18:15

4 Answers4

1

There are exists two ways to reset Meyer's singleton:

#include <new>

class Singleton
{
public:
    static Singleton& instance() {
        static Singleton instance;
        return instance;
    }
    virtual ~Singleton() = default;

    // Option 1 - easy and convinient if Singleton is copyable or movable
    void reset1() {
        *this = {};
    }
    // Option 2 - works always
    void reset2() {
        this->~Singleton();
        new (this) Singleton;
    }

private:
    Singleton() {}
};

2nd option is basically doing exactly what you are doing with releasing/creating std::unique_ptr, except without deallocation/allocation of storage.

sklott
  • 2,634
  • 6
  • 17
  • I have a `this is unavailable for static member functions` error – Moulagaufres Jan 27 '23 at 11:18
  • This has another advantage over the unique_ptr method: If somebody holds a reference to the singleton (which seems a sufficiently sensible thing to do), their reference won't be invalidated by the `reset()`. – Chronial Jan 27 '23 at 13:45
  • @Moulagaufres If you want `reset()` to be `static` then use `&instance()` instead of `this`. – sklott Jan 27 '23 at 16:42
0
  1. You'd get lazy instance creation if you use a pointer as opposed to a reference.
  2. If the reset() is a logical reset, you won't need a pointer, a reference would do but, if the requirement is that of a memory reset, you'll need it.
  3. Since the lifetime of the static singleton instance is the whole application lifetime, you don't need to care about the RAII benefit that you get with unique ptr.
  4. But, you'd use unique_ptr to indicate exclusive ownership over the resource and some guidelines forbid using raw pointers for non-owning
  5. The current code allows copying and moving of your instance, breaking the Singleton invariances.
helix
  • 1,017
  • 3
  • 12
  • 30
-1

There is no need to use std::unique_ptr. Use references

class Singleton
{
public:
    static Singleton& instance();
    virtual ~Singleton() = default;

    // Used for unit tests
    void reset();
private:
    Singleton();
    int stuff;
};

Singleton& Singleton::instance()
{
    static Singleton instance;
    return instance;
}

void Singleton::reset() { stuff = 0; }

// Private constructor
Singleton::Singleton() {}

Something Something
  • 3,999
  • 1
  • 6
  • 21
-1

Note that this design is dominated by your requirement for a reset function. This prevents you from using certain Singleton implementations, like e.g. Meyer's Singleton.

In general you would want a singleton implementation to not allow the implementation of a reset method, because now your singleton is not really a singleton anymore. But it might of course make sense to "water down" the singleton for testing purposes.

If multithreading-safety is not a concern, your singleton implementation is overly complicated and could be reduced to:

class Singleton
{
public:
  static Singleton& get() {
    if (!mInstance) {
        mInstance = std::unique_ptr<Singleton>(new Singleton());
    }
    return *mInstance;
  }
  
  static void reset() { mInstance.reset(); }

private:
  static inline std::unique_ptr<Singleton> mInstance{};
};
Chronial
  • 66,706
  • 14
  • 93
  • 99