Skip to content

C++11/17/20 thread-safe singleton pattern using CRTP

License

Notifications You must be signed in to change notification settings

jimmy-park/singleton

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

singleton

CI CodeQL

Implement thread-safe singleton classes using Curiously Recurring Template Pattern (CRTP)

CMake Options

Option Default Description
SINGLETON_INJECT_ABSTRACT_CLASS OFF Prevent construction of derived class at compile time
SINGLETON_INSTALL OFF Install headers and CMake targets

Usage

Build

cmake --list-presets all                    # List all CMake presets
cmake --preset windows                      # Configure
cmake --build --preset windows              # Build
ctest --preset windows                      # Test
cmake --build --preset windows -t install   # Install

Integration

include(FetchContent)

set(SINGLETON_INJECT_ABSTRACT_CLASS ON)
set(SINGLETON_INSTALL ON)

FetchContent_Declare(
    singleton
    URL https://github.com/jimmy-park/singleton/archive/main.tar.gz
)
FetchContent_MakeAvailable(singleton)

# If you're using CPM.cmake
# CPMAddPackage(
#     NAME singleton
#     URL https://github.com/jimmy-park/singleton/archive/main.tar.gz
#     OPTIONS
#     "SINGLETON_INJECT_ABSTRACT_CLASS ON"
#     "SINGLETON_INSTALL ON"
# )

add_executable(main main.cpp)
target_link_libraries(main PRIVATE 
    singleton::singleton        # C++11
    singleton::singleton-dclp   # C++17
    singleton::singleton-atomic # C++20
)

Example

C++11

Rely on initialization of static local variable

#include <singleton.hpp>

struct Foo : public Singleton<Foo> {
    void Bar() {}
};

int main()
{
    // Compile error when SINGLETON_INJECT_ABSTRACT_CLASS is ON
    // Foo foo;

    Foo::GetInstance().Bar();
}

C++17

Implement based on Double-Checked Locking Pattern (DCLP)

Use this version when you need to initialize with parameters or control the destruction order manually

  • C++17 features
    • Inline static member variable
    • If statements with initializer
    • std::shared_mutex
#include <singleton_dclp.hpp>

class Foo : public SingletonDclp<Foo> {
public:
    Foo(int n) : n_ { n } {}
    void Bar() {}

private:
    int n_;
};

int main()
{
    Foo::Construct(17);
    Foo::GetInstance()->Bar();
    Foo::Destruct();
}

Caveats

  • GetInstance() must be called between Construct() and Destruct()
  • Don't forget to call Destruct() before terminating program

C++20

Use std::atomic::wait() to block GetInstance() during construction

#include <singleton_atomic.hpp>

class Foo : public SingletonAtomic<Foo> {
public:
    Foo(int n) {}
    void Bar() {}
};

int main()
{
    std::jthread t { [] {
        std::this_thread::sleep_for(std::chrono::seconds { 1 });
        Foo::Construct(20);
    } };                        // Construct in another thread
    Foo::GetInstance()->Bar();  // Block until construction is finished
}

Reference