1

I have tried for the past days to solve this issue I have with C++. It may be trivial, but I could not find a solution and searching on the Internet got me nowhere, so I'll ask here.

I have a C++ wrapper Singleton class, a superclass and a number of subclasses. I need the the instances of the subclasses to be singletons, and so I used the solution proposed here as it is the one fitting my needs the most. I need a map to act as a register from a string to the correct subclass. To make it concrete, here is the code:

// The Singleton class
template <typename T>
class Singleton
{
public:
    static T* Instance()
    {
        if (s_instance == NULL) { s_instance = new T(); }
        return s_instance;
    }
protected:
    static T* s_instance;
}

// Superclass
class MySuperclass
{
public:
    inline LPCSTR GetName() { return m_name; }
    
    virtual ~MySuperclass() { }
protected:
    LPCSTR m_name;
    
    MySuperclass(LPCSTR name) { m_name = name; }
}

// Example subclass
class MySubclass : public MySuperclass
{
    // subclass stuff
}

Now, I have tried a number of things, let me show all that I have tried:

// Where the problems begin
class MyRegister
{
public:
    static void Register()
    {
        Singleton<MySubclass> mySubclassSingleton;
        LPCSTR name = Singleton<MySubclass>::Instance()->GetName();
        s_classes.insert(std::make_pair(name, mySubclassSingleton));
    }
private:
    static std::unordered_map<LPCSTR, Singleton<MySuperclass>> s_classes;
}

This is the version I'm stuck with and it gives an error on the insert saying:

E0304 no instance of overloaded function "std::map<_Kty, _Ty, _Pr, _Alloc>::insert [with _Kty=LPCSTR, _Ty=Singleton<MySuperclass>, _Pr=std::less<LPCSTR>, _Alloc=std::allocator<std::pair<const LPCSTR, Singleton<MySuperclass>>>]" matches the argument list

I have tried using std::pair instead of std::make_pair, changed the definition of the map to:

template <class T : public MySuperclass>
std::unordered_map s_classes<LPCSTR, Singleton<T>> s_classes;

But to no avail, as the first resulted in the same error (also using the [] operator gave me issues with [name] as no operator matches these operands) and the second results in type errors.

As it stands, I need the classes to be singletons, and to ensure that they are singletons, and I need a register that links a unique string identifying the class to its singleton. Can anyone either explain why this is impossible or if there is something like Java's <? extends MySuperclass> in C++ that can work here?

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Sandro Massa
  • 98
  • 1
  • 10

1 Answers1

1
template <class T : public MySuperclass>
std::unordered_map s_classes<std::string, Singleton<T>> s_classes;

is not valid C++. But you could simply make a std::unordered_map<std::string, MySuperclass*> s_classes. It's not necessary for our lookup map to know about the Singleton pattern, it just needs to map onto a pointer to the corresponding subclass. We must use std::string or std::wstring as a key, because otherwise pointer comparison would be used between keys, not string comparison. Alternatively, we could provide a custom comparison operator.

The Singleton itself can also be simplified. static local variables are initialized upon the first use of the function, which is exactly the behavior that we want.

template <typename T>
class Singleton
{
    static T *Instance()
    {
        static T instance{};
        return &instance;
    }
}

Our previous use of new would have been a memory leak anyways, since delete was never called.

Now, we can also update or MyRegister accordingly.

class MyRegister
{
public:
    static void Register()
    {
        MySubclass *instance = Singleton<MySubclass>::instance();
        s_classes.emplace(instance->GetName(), instance);
    }
private:
    static std::unordered_map<std::string, MySuperclass*> s_classes;
}
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
  • Technically the Meyer's singleton pattern is just as much of a "memory leak" as OP's method; both objects will live until the application exits. I wouldn't really consider it to be a leak since singletons by design are not meant to be deleted. – 0x5453 May 06 '21 at 14:05
  • 1
    @0x5453 that's not true. If you don't call `delete`, the destructor is not called and depending on it is UB: https://stackoverflow.com/a/9921320/5740428 . However, it is guaranteed that objects with `static` storage duration will have their destructors called upon `return` from `main` or `exit()`. https://stackoverflow.com/a/2204628/5740428 – Jan Schultke May 06 '21 at 14:09
  • UB if you rely on side effects from the destructor, which is defaulted in this case. But in general you are correct and that is an important distinction to make. – 0x5453 May 06 '21 at 14:12
  • Thanks @JanSchultke! Tried and worked perfectly fine. Also thanks for the explanation and the upgrade of the Singleton patttern!!! – Sandro Massa May 06 '21 at 14:39