ProgrammingC++ Developer

What is the 'Singleton' design pattern in C++? How to implement it correctly and what are the main pitfalls?

Pass interviews with Hintsage AI assistant

Answer.

Background:

The Singleton pattern was proposed to restrict the creation of only one instance of a specific class, which was needed for implementing global managers (e.g., loggers, resource pools, configuration managers).

Problem:

Implementing a Singleton seems straightforward, but challenges arise in C++: thread safety, destruction correctness, initialization order.

Solution:

The safest and most modern way to implement a Singleton uses a static local variable within a static function, which guarantees initialization upon first access, thread safety (since C++11), and correct destruction.

Sample code:

class Singleton { public: static Singleton& instance() { static Singleton s; return s; } void doSomething() {} private: Singleton() {} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; };

Key features:

  • Guarantees the existence of only one instance of the class.
  • Lazy object creation and thread safety (since C++11).
  • Control over the lifetime and destruction of the instance.

Trick Questions.

Can a second Singleton instance be created via serialization or cloning?

Yes. If serialization/deserialization methods are implemented or clone() is manually implemented without restricting the copy constructor, a second instance may be created. To avoid this, all forms of copying, cloning, and restoring via serialization should be prohibited.

Will the Singleton be correctly implemented in a multithreaded environment in C++98/03 standard using a local static variable?

No. Local static variables before C++11 did not guarantee thread safety during initialization. This could lead to multiple instances being created if two threads simultaneously entered the instance() function. In C++11 and later, this issue is resolved at the standard level.

When is the Singleton instance created through a static local variable destroyed?

The object is destroyed in reverse order of creation (LIFO) during the program's termination (exit). However, this may lead to issues if the destructor accesses objects that have already been destroyed.

Common Mistakes and Anti-patterns

  • Using new instead of static variables, resulting in leaks.
  • Not prohibiting copying/assignment.
  • Ignoring threads (in older standards).
  • Using shared_ptr or weak_ptr to hold the Singleton instance (violating uniqueness).

Real-life Example

Negative Case

In a logging system, a developer implements Singleton using a global pointer and new, forgetting to prohibit copying. The program works, but in a multithreaded environment different logger instances sometimes arise, and messages are lost.

Pros:

  • Simplest implementation; works fast in a single-threaded mode.

Cons:

  • Potential memory leaks.
  • Violation of instance uniqueness in threads.
  • Destruction issues.

Positive Case

A Singleton is implemented using a static local variable in a static function, and copying is prohibited. Thread safety is ensured, the program scales, and logs are correctly separated.

Pros:

  • Guarantees uniqueness under all conditions.
  • Correct destruction.
  • No memory leaks.

Cons:

  • More challenging to conduct testing (for unit tests, substituting singleton is difficult).