Freitag, 7. Juni 2013

A thread-safe C++ Singleton for Unittesting

The Singleton pattern became infamous in multithreaded C++. A lot of issues arise with this pattern in  the constructor when multiple threads try to get an instance all at once for the first time. And there comes even more trouble along the road with properly deconstructing the singleton-object.

But still, the singleton is a viable pattern in terms of software design. Just because you cannot build it with the tools at hand does not necessarily make it a bad pattern. With C++11 it is possible to get rid of some mayor drawbacks implementation wise. Let's first have a look at the implementation we came up with at Xcessity.

Implementation

Header File:

#include <memory>
#include <mutex>

class CSingleton
{
public:
  virtual ~CSingleton();
  static CSingleton& getInstance();

  static void destroyInstance();

private:
  static std::unique_ptr<CSignleton> mInstance;
  static std::once_flag mOnceFlag;


  CSingleton();
  CSingleton(const CSingleton&);
  CSingleton& operator=(const CSingleton&);


#ifdef _UNITTEST
public:
  static void reset() { mInstance.reset(new DeviceManager); }
#endif
  
  std::vector<SomeClass> mPrivateCollection;
};


Source File:

std::unique_ptr<CSingletonCSingleton::mInstance = nullptr;
std::once_flag CSingleton::mOnceFlag;

CSingletonCSingleton::getInstance()
{
  std::call_once(mOnceFlag,
    [] { mInstance.reset(new CSingleton); });

  return *mInstance.get();
}

void CSingleton::destroyInstance()
{
  mInstance.reset();
}


Explanation

This implementation is inspired by Marc Gregoire's post on "Implementing a Thread-safe Singleton with C++". You can follow the link to read more about the basic implementation. Let's look at the added features.

CSingleton::destroyInstance()

You may be aware of the more common approach to create the singleton instance by using a static variable. The benefit of the pointer based approach is that you can choose when the singleton get's destroyed at the end of your application. Sometimes it is desirable to choose the exact order in which your components get destroyed. Of course you have to ensure that nothing else accesses the singleton during or after this call. Typically you want to do this at the end of runtime with only the main thread active.

Additionally the unique_ptr will take care of deleting the singleton object in case you don't care about the order of deconstruction. So even if you don't call destroyInstance() the singleton gets properly deleted.

#ifdef, #endif

One argument against singletons is that they are hard to test using unittests. In unittesting you try to test one method after another in separate tests. The problem arising is that each test performs its operations on the same singleton object. So whenever a test alters the state of the singleton it is propagated to the following tests. This may lead to undesired behavior and can lead to false testing results.

Using reset() at the beginning of every unittest solves this issue. In each test you get a nice and clean singleton object ready for testing. By defining _UNITTEST in your test build environment this method only get's exposed for testing purposes.

#ifdef & public:

In unittesting it is beneficial to test only single methods. Of course it is important to use the public interfaces. Ultimately you want to call the method and check if the invariant of the object is as promised by the method. Having access to the private members gives you the chance to check these conditions. Many approaches exist to achieve this behavior, but this is one of the least intrusive ones.

It is important not to include the static members in your public section for unittesting. It would cause linker errors to make the static members public just in the header files when using a static library for example.

Conclusion

With this implementation we can solve the following issues of the singleton pattern:

  1. thread-safety
  2. random deconstruction order
  3. unittesting a singleton
  4. access to private members for unittesting

Hopefully you found some inspiration in this implementation. Any suggestions on improvements are welcome.