Cooking C++ Singleton. The right way.

Let me share ideas how to use singletons in C++ without its known drawbacks. Don’t hesitate to give some feedback.

Summary

Usually application has some global objects which can be used in different components on different levels\layers. E.g. config, engine, tasks scheduler etc. It’s desirable to have a possibility to pass such global objects to any component and layer of the app easily and have a possibility to set some other implementation of the object if required(mock for tests).

Challenge

There are two approaches

Pass & store ref to global object.

E.g. config is created in main function and passed to all required places though ctors or methods args. So we end up with a code where global objects\interfaces are sticking around.

Use some kind of global objects

ObjType& GetInstance() {
 static ObjType instance;
 return instance;
}

If this function is called somewhere in the beginning of the main then we will have predictable init order. If code will be used before any thread is created then we don’t care about data races on construction of the static value, thus this is usable even in pre-C++11. But there are issues: hard to mock, destruction order is undefined and destruction starts after main when some parts of system can be already destroyed.

Solution

Two simple ideas “template tagging” and “accessor” allow to keep benefits of global objects and remove all drawbacks. Tagging is implemented in the following way

template <typename T, typename Tag = T>
T& single() {
 static T t;
 return t;
}

This allows to get multiple singletons of the same type

class CustomTag;
auto& foo = single<Foo>();
auto& custom_foo = single<Foo, CustomTag>();

Accessor has the following implementation

template <typename T, typename Tag = T>
class SingleAccessor {
public:
 void Attach(T& single);
 void Detach();
 bool GetIsAttached() const;
 operator T&();
 T& GetRef();
private:
 T* ptr_single_ = nullptr;
};

Usage sample

Define accessor(pay attention that accessor references to interface but not to the implementation)

using IoServiceAccessor =SingleAccessor<IIoService>;

IoServiceAccessor& GetDefaultIoServiceAccessorInstance() {
  return single<IoServiceAccessor>();
}

Somewhere in main

using IoServiceAccessor = SingleAccessor<IIoService>;

thread_pool_ = util::make_unique<ThreadPool>(thread_pool_size, "main");
GetDefaultIoServiceAccessorInstance().Attach(*thread_pool_);
GetDefaultSchedulerAccessorInstance().Attach(*thread_pool_);

// Do the job
// Access this way
auto& asio_service =
  GetDefaultIoServiceAccessorInstance().GetRef().GetAsioService();
// ...
GetDefaultIoServiceAccessorInstance().Detach();
GetDefaultSchedulerAccessorInstance().Detach();

See the following links for implementation details

Benefits

Construction and destruction of the objects are well defined. Since accessor is defined basing on interface we can replace implementation easily and this way pass desired implementation to any component\layer. Tagging allows to have few global objects E.g. “main scheduler” for common tasks, “network scheduler” for network io tasks etc.

Disadvantages

I see no disadvantages, except the fact that such approach might encourage a developer to create too much singletons so app will become more complicated (architecture not clear and straightforward).

go-back