From 107d5798b72d5822b1cdb69e51a3a217fc1a0a27 Mon Sep 17 00:00:00 2001 From: K1ngst0m Date: Sun, 29 Oct 2023 00:14:45 +0800 Subject: [PATCH] add singleton base class --- engine/common/singleton.h | 33 ++++++++++++++++++ tests/singleton.cpp | 70 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 engine/common/singleton.h create mode 100644 tests/singleton.cpp diff --git a/engine/common/singleton.h b/engine/common/singleton.h new file mode 100644 index 00000000..a98ea671 --- /dev/null +++ b/engine/common/singleton.h @@ -0,0 +1,33 @@ +#ifndef APH_SINGLETON_H +#define APH_SINGLETON_H + +#include +#include +#include + +namespace aph +{ + +template +class Singleton +{ +public: + static Derived& GetInstance() + { + // Meyers' Singleton with guaranteed thread-safety since C++11 + static Derived instance; + return instance; + } + + Singleton(const Singleton&) = delete; + Singleton& operator=(const Singleton&) = delete; + Singleton(Singleton&&) noexcept = delete; + Singleton& operator=(Singleton&&) noexcept = delete; + +protected: + Singleton() = default; + virtual ~Singleton() = default; +}; + +} // namespace aph +#endif diff --git a/tests/singleton.cpp b/tests/singleton.cpp new file mode 100644 index 00000000..1a74de6d --- /dev/null +++ b/tests/singleton.cpp @@ -0,0 +1,70 @@ +#include "common/singleton.h" +#include +#include +#include + +using namespace aph; + +class MySingleton : public Singleton +{ + friend class Singleton; + +public: + void setValue(int v) { value = v; } + int getValue() const { return value; } + +private: + MySingleton() = default; + int value{}; +}; + +TEST_CASE("Basic Usage of Singleton", "[Singleton]") +{ + SECTION("Ensure we can create an instance") + { + MySingleton& instance1 = MySingleton::GetInstance(); + instance1.setValue(10); + REQUIRE(instance1.getValue() == 10); + } + + SECTION("Ensure repeated calls return same instance") + { + MySingleton& instance1 = MySingleton::GetInstance(); + MySingleton& instance2 = MySingleton::GetInstance(); + REQUIRE(&instance1 == &instance2); // Compare addresses + } +} + +TEST_CASE("Thread Safety of Singleton", "[Singleton]") +{ + std::vector threads; + std::vector addresses; + + // Mutex to ensure synchronized access to the addresses vector + std::mutex mtx; + + const int numThreads = 100; + + for (int i = 0; i < numThreads; ++i) + { + threads.push_back(std::thread([&]() + { + MySingleton& instance = MySingleton::GetInstance(); + std::lock_guard lock(mtx); + addresses.push_back(&instance); + })); + } + + for (auto& thread : threads) + { + thread.join(); + } + + SECTION("All threads get the same instance") + { + for (std::size_t i = 1; i < addresses.size(); ++i) + { + REQUIRE(addresses[i] == addresses[0]); + } + } +}