Implementing Singleton with CRTP
Asked Answered
M

4

5

After reading this answer I have tried implementing some simple CRTP usage. I figured I'd try to implement the Singleton (yes, I know - it's just for practice and research) pattern, given the fact that the linked answer kind of already does it... except for the fact that it does not compile.

The quoted code is as follows:

template <class ActualClass> 
class Singleton
{
   public:
     static ActualClass& GetInstance()
     {
       if(p == nullptr)
         p = new ActualClass;
       return *p; 
     }

   protected:
     static ActualClass* p;
   private:
     Singleton(){}
     Singleton(Singleton const &);
     Singleton& operator = (Singleton const &); 
};
template <class T>
T* Singleton<T>::p = nullptr;

class A: public Singleton<A>
{
    //Rest of functionality for class A
};

Which I then 'modernized' to:

template <class T>
class Singleton {
public:
    Singleton()                              = delete;
    Singleton(const Singleton&)              = delete;
    Singleton(Singleton&&)                   = delete;
    Singleton& operator = (const Singleton&) = delete;
    Singleton& operator = (Singleton&&)      = delete;

    static T& get_instance() {
        if(!instance)
            instance = new T;
        return *instance;
    }

   protected:
     static inline T* instance = nullptr;
};

class A: public Singleton<A> {
    //Rest of functionality for class A
};

I then tried to create a reference to the instance:

auto& x = A::get_instance();

which obviously did not compile.

It's worth mentioning that I get very similar error messages, notably:

note: 'A::A()' is implicitly deleted because the default definition would be ill-formed: class A : public Singleton<A>.

Obviously, the second snippet of code cannot compile, since we deleted the default constructor and try to use it with new T in the get_instance method.

What surprises me is that the first snippet doesn't compile either, with similar error messages. Does the linked answer has a mistake? How would I implement a generic base class / interface for Singletons using CRTP?

Megillah answered 22/8, 2018 at 20:31 Comment(3)
Have you considered not implementing the singleton anti-pattern and saving yourself a lot of grief down the line?Exothermic
@JesperJuhl, hm, have you considered reading the "yes, I know - it's just for practice and research" part? I have no intention of using this in real code. Besides, the usefulness of Singleton pattern is not the topic of this question. I encourage you to read the 6th comment below the linked answer - the one with 45 upvotes.Megillah
I still consider it a bad choice of practice problem.Exothermic
H
3

Here's mods to the "modernized" snippet:

template <class T>
class Singleton {
public:
    Singleton& operator = (const Singleton&) = delete;
    Singleton& operator = (Singleton&&)      = delete;

    static T& get_instance() {
        if(!instance)
            instance = new T_Instance;
        return *instance;
    }

protected:
    Singleton() {}

private:
    struct T_Instance : public T {
        T_Instance() : T() {}
    };

    static inline T* instance = nullptr;
};

class A : public Singleton<A> {
protected:
    A() {}
};

int main()
{
    auto& x = A::get_instance();
}

Summary of changes from snippet:

  • protected default constructor in singleton
  • private nested struct to access derived class's protected constructor
  • protected constructor in derived class to prevent instantiation

Also, no need to delete constructors that are implicitly deleted by adding default ctor implementation to Singleton class.

Not so small as Richard Hodges' example, but the static instance member makes it easy to add a delete_instance() method for use in automated unit tests.

Horseshit answered 1/5, 2019 at 6:9 Comment(1)
The struct T_Instance : public T trick will not work when T is declared final.Deuteranopia
D
3

Smallest possible (I think) implementation.

Features:

  • A is neither copyable, constructible or movable. (move operators implicitly deleted by deleting copy ops)
  • construction of the implementation is thread-safe.
  • destruction of the implementation is assured at program end.

 

template <class T>
struct Singleton 
{
    Singleton(const Singleton&)              = delete;
    Singleton& operator = (const Singleton&) = delete;

    static T& get_instance() {
        static T _{allow()};
        return _;
    }

private:
    struct allow {};

protected:
    Singleton(allow) {}
};

class A: public Singleton<A> {
    using Singleton<A>::Singleton;
    //Rest of functionality for class A
};

int main()
{
    auto& x = Singleton<A>::get_instance();
    auto& y = A::get_instance();

// compiler error
    auto z = A();
}

But why not make 'singleton-ness' an implementation detail? Why should users need to know that the object is a singleton?

template <class T>
struct Singleton 
{
protected:
    static T& get_impl() {
        static T _;
        return _;
    }
};

// the real implementation of A
struct AImpl
{
    void foo();
};

// A is a value-type which just happens to be implemented in terms of a
// single instance
struct A: public Singleton<AImpl> 
{
    auto foo() { return get_impl().foo(); }
};

void bar(A a)
{
    a.foo();
}

int main()
{
    auto x = A();
    x.foo();

    auto y = A();
    y.foo();

    x = y;

    bar(x);
}

Then later, if you decide that the type should not be a singleton, you don't need to change its interface (and therefore the rest of your program):

Example - A is a singleton and B is not. The interfaces are identical.

#include <memory>

template <class T>
struct Singleton 
{
protected:
    static T& get_impl() {
        static T _;
        return _;
    }
};

template<class T>
struct CopyableIndirect
{
    CopyableIndirect() = default;

    CopyableIndirect(CopyableIndirect const& r)
    : impl_(std::make_unique<T>(*r.impl_))
    {

    }

    CopyableIndirect(CopyableIndirect&& r)
    : impl_(std::move(r.impl_))
    {

    }

    CopyableIndirect& operator=(CopyableIndirect const& r)
    {
        auto temp = r;
        swap(temp);
        return *this;
    }

    CopyableIndirect& operator=(CopyableIndirect && r)
    {
        auto temp = std::move(r);
        swap(temp);
        return *this;
    }

    void swap(CopyableIndirect& r)
    {
        std::swap(impl_, r.impl_);
    }
protected:
    T& get_impl() {
        return *impl_;
    }

    T const& get_impl() const {
        return *impl_;
    }

   std::unique_ptr<T> impl_ = std::make_unique<T>();
};

struct AImpl
{
    void foo() const;
};

struct A: public Singleton<AImpl> 
{
    auto foo() const { return get_impl().foo(); }
};

struct B: public CopyableIndirect<AImpl> 
{
    auto foo() const { return get_impl().foo(); }
};

void bar(A const& a)
{
    a.foo();
}

void bar(B const& a)
{
    a.foo();
}

int main()
{
    auto x = A();
    x.foo();

    auto y = B();
    y.foo();

    bar(x);
    bar(y);
}
Deangelis answered 22/8, 2018 at 22:50 Comment(5)
Regarding your first snippet of code, uncommenting the auto z = A(); does not result in a compilation error. Regarding your second snippet, I fail to see how that's a singleton. Regarding your third snippet, pretty much the sameMegillah
@Megillah you're right, the default constructor is inherited by A. The point of snippets 2 and 3 is that the singleton nature of the implementation is an irrelevsnt detail from the point of view of all users of the object, since all copies of the (stateless) object are equivalent and refer to the same implementation.Deangelis
Visual Studio 2015 fails to compile the first snippet with error C2248: 'A::A': cannot access protected member declared in class 'A' on line 8 in get_instance. Changing the Singleton(allow) access modifier to public fixes the error and auto z = A(); still fails to compile as intended. Also tested in godbolt.Spital
@Spital the code at the link you have given does not seem to relate to this answer?Deangelis
@RichardHodges I just gave a link to the main page of the website. I copy pasted the code from the answer as is (commenting the auto z = A();). Here is a link with the code. Fails to compile only on msvc compiler.Spital
H
3

Here's mods to the "modernized" snippet:

template <class T>
class Singleton {
public:
    Singleton& operator = (const Singleton&) = delete;
    Singleton& operator = (Singleton&&)      = delete;

    static T& get_instance() {
        if(!instance)
            instance = new T_Instance;
        return *instance;
    }

protected:
    Singleton() {}

private:
    struct T_Instance : public T {
        T_Instance() : T() {}
    };

    static inline T* instance = nullptr;
};

class A : public Singleton<A> {
protected:
    A() {}
};

int main()
{
    auto& x = A::get_instance();
}

Summary of changes from snippet:

  • protected default constructor in singleton
  • private nested struct to access derived class's protected constructor
  • protected constructor in derived class to prevent instantiation

Also, no need to delete constructors that are implicitly deleted by adding default ctor implementation to Singleton class.

Not so small as Richard Hodges' example, but the static instance member makes it easy to add a delete_instance() method for use in automated unit tests.

Horseshit answered 1/5, 2019 at 6:9 Comment(1)
The struct T_Instance : public T trick will not work when T is declared final.Deuteranopia
R
1

The issue with your first block of code is Singleton(){} is marked private. This means A has no access to it so A cannot be default construct. Making the constructor protected will fix that

template <class ActualClass> 
class Singleton
{
   public:
     static ActualClass& GetInstance()
     {
       if(p == nullptr)
         p = new ActualClass;
       return *p; 
     }

   protected:
     static ActualClass* p;
     Singleton(){}
   private:
     Singleton(Singleton const &);
     Singleton& operator = (Singleton const &); 
};
template <class T>
T* Singleton<T>::p = nullptr;

class A: public Singleton<A>
{
    //Rest of functionality for class A
};

int main()
{
    auto& x = Singleton<A>::GetInstance();
}

Your second code bock has a similar issue but instead of the default construct being private you have marked it as delete so it is not =default constructable meaning A will also not be default constructable. defaulting the constructor are making it protected like the first example will fix that

template <class T>
class Singleton {
public:

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

    static T& get_instance() {
        if(!instance)
            instance = new T;
        return *instance;
    }

protected:
    Singleton()                              = default;
    static inline T* instance = nullptr;
};

class A: public Singleton<A> {
    //Rest of functionality for class A
};

int main()
{
    auto& x = Singleton<A>::get_instance();
}
Rath answered 22/8, 2018 at 20:44 Comment(7)
It also makes creating objects of a type A possible, which is the very thing I want to avoid while implementing a singletonMegillah
@Megillah You just need to make A's constructor protected as well.Rath
If I do so, your code stops compiling with 'constexpr A::A()' is protected within this context error.Megillah
@Megillah I forgot about that. You'd also have to add friend class Singleton<A>; to A.Rath
So that means that the originally linked answer does have mistakes in it? It lacks both the protected constructor in derived class and the friend declaration. Does the // Rest of functionality for class A imply them? Nonetheless, thank you, you've been very helpfulMegillah
@Megillah The linked answer does have a protected default constructor in Singleton. The friend part was probably a missed observation like I did.Rath
But it does not have the protected constructor in the derived class, which makes the code pointlessMegillah
I
0

Here is my implementation for single thread singleton.

#include <cassert>
#include <stdexcept>
#include <type_traits>

template <typename T>
class Singleton {
public:
    template <typename... Args>
    static void init(Args&&... args) {
        static_assert(!std::is_copy_constructible_v<T>);
        static_assert(!std::is_move_constructible_v<T>);
        static_assert(!std::is_copy_assignable_v<T>);
        static_assert(!std::is_move_assignable_v<T>);

        if (s_instance) {
          throw std::logic_error("Singleton should not be initialized twice");
        }

        s_instance = new T(std::forward<Args>(args)...);
    }

    static void shutdown() {
        delete s_instance;
        s_instance = nullptr;
    }

    static T& getInstance() noexcept {
        assert(("Singleton should be initialized before use", s_instance != nullptr));
        return *s_instance;
    }

    Singleton(const Singleton&) = delete;

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

protected:
    static inline T* s_instance{ nullptr };

    Singleton() = default;

    ~Singleton() noexcept = default;
};

class A : public Singleton<A> {
  // some functionalities
private:
    A() = default;
    ~A() = default;
    friend class Singleton<A>;
};
Icsh answered 5/4, 2024 at 19:30 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.