I know Singleton is not good and that in general the thread safe lazy initialization in modern C++ can be done by static local variable, but I have quite specific use case and although I can use safer “less-performant” techniques(double check with mutex for the second check), I still would love to verify my understandings on atomic variables and memory ordering.
Also the main reason I cannot use static local variable is because it needs to be shared between DLL/SO boundaries.
This is the code which came to my mind as the most performant lock-free solution without using stronger memory barriers.
It seems to work, but I do wonder if that is really the case.
Is my usage of memory ordering correct here? Will it have the expected behavior?
// in header file
class DLL_EXPORT MyLogger
{
public:
void logMessage(std::string message);
private:
static std::atomic<MyLogger*> sSharedInstance;
}
// in cpp file
constinit std::atomic<MyLogger*> MyLogger::sSharedInstance{nullptr};
MyLogger& MyLogger::sharedInstance()
{
MyLogger* logger = sSharedInstance.load(std::memory_order_relaxed);
if (!logger)
{
logger = new MyLogger();
MyLogger* previousLogger = nullptr;
if (!sSharedInstance.compare_exchange_strong(previousLogger, logger, std::memory_order_relaxed, std::memory_order_relaxed))
{
// Lost the init race - destroy the redundant logger
delete logger;
logger = previousLogger;
}
}
return *logger;
}
// in different threads
void someFunction();
{
// Can be called from many threads...
MyLogger::sharedInstance().logMessage("some message");
}