Singleton Design Patterns in C++

Singleton Design Patterns in C++

If you need a shared resource that is used for various sections of the application, like a database connection, a singleton will provide you with a guarantee of one instance being shared with the resource. 

Now in this article, we are going to explain the singleton design pattern, steps to implement singleton design patterns in C++ along with its testing and best practices so that you will become proficient with this concept of C++.

Table of Contents:

What are Singleton Design Patterns? 

The Singleton Design Pattern makes sure that only one instance is created throughout the program and provides the global access point to that instance. This singleton method is used only when exactly one object is needed to perform the actions across the program. 

Uses of Singleton Design Pattern:

  1. Singleton ensures a single instance that is globally accessible throughout an application
  2. Singleton helps manage shared resources like database connections, file systems, and network connections.
  3. The Singleton method is also used for managing the cache, it ensures only one cache is shared across the application. 
  4. The logging method has a single instance across the application to control the log file.

Types of Singleton Design Patterns 

There are 4 common types of singleton design patterns, they are: 

1. Eager Initialization of Singleton Design Pattern

The instance of a class is created when the class is loaded, i.e. at the beginning of the program. This is how the object is created, even if never used. 

Example:

Cpp

In this code, the Singleton class has a static instance (Singleton instance) that is created eagerly at the time of the program start. The getInstance() method provides the static instance reference, confirming a single instance of the class. 

2. Lazy Initialization of Singleton Design Pattern

The class instance is created only when it is needed for the first time. It is only instantiated when the getInstance() function is called for the first time. 

Example:

Cpp

In this code, the Singleton class has a static pointer (Singleton* instance), that will be initialized only to nullptr. If the instance is nullptr (not created yet), the getInstance() method creates a new instance of the class using new. This way only one instance of the class is created lazily.

3. Thread-Safe 

A variation of the Lazy Initialization Singleton where instance creation is protected, thus preventing multiple threads from simultaneously creating the instance by using mutex locks. 

Example: 

Cpp

However, this method is a thread-safe Singleton pattern with a lazy initialization of instance using a std::mutex to ensure that multiple threads are not able to create multiple singletons at the same moment in time. Then we create an instance using the std::lock_guard locking a mutex during the instance creation.

4. Static Local Variable 

Since C++ 11 and later, this method ensures the thread-safety when the local static variable is created, and the lazy initialization approach makes sure to create only one instance using getInstance().

Example: 

Cpp

In the above code, the singleton class uses a static local variable instance inside the getInstance() method, ensuring that only one instance is created and it is thread-safe in C++ 11 and later. 

Steps to Implement Singleton Pattern in C++

There are 4 steps to implement the singleton pattern in C++ 

Step 1. Private Constructor: Ensuring Single Instance Creation 

The constructor should be declared private to prevent the rest of the program from creating an object of the singleton class. Rather than creating several instances, this method ensures that only the singleton class itself can create and have control of the single instance.

Note: If there were no private constructor here, then other parts of the program could readily create instances with a new or default constructor, which is against the singleton principle.

Example:

Cpp

In this example, the constructor is private, so no one can create an instance that is outside of the class.

Step 2. Static instance: Storing the Single Instance 

The static instance is required because it is a class variable that can contain only a single instance. It provides the guarantee of having only one instance during the lifetime of the application. The static instance provides the facility to reuse the same instance in different calls, and the Singleton pattern is maintained.

Example:

Cpp

The static instance is important because if the class had a regular instance, each time the getInstance() method is called, it leads to the creation of a new instance.

Step 3. Public Static Method: Accessing the Singleton Instances 

The public static method uses the getInstance() to provide the global access point to the Singleton instance. This method makes sure that only one instance is created and shared throughout the program.

Example: 

Cpp

The getInstance() method checks whether the instance exists or not. If it doesn’t exist, it creates a new instance. If it does exist, it returns the existing instance.

Step 4. Ensuring Singleton Integrity: Preventing Multiple Instances 

Maintaining singleton integrity is the final thing when implementing the singleton design pattern of the singleton instance. This function avoids duplicating singleton instances, primarily in multi-threaded environments where the singleton ends up getting duplicated accidentally. If the singleton class is utilized in a multi-threaded environment, you need to make sure the getInstance() method is thread-safe.

Example:

Cpp

Output: 

The getInstance() method ensures that only one instance of the singleton class is created, showMessage() method prints a message. The if condition (instance1 == instance2) checks whether both pointers point to the same instance. 

Implementing Singleton Design Pattern Using Logger Class

This design pattern ensures that only one instance of the logger throughout the lifetime of the program, which we can control globally. Without creating multiple instances logger makes sure to create only one instance. The logger is very essential for logging events, and errors.

Example:

Cpp

Output: 

This program uses the Singleton pattern for a Logger class such that there is only one instance used in multi-threaded environments. It includes methods to log messages of different kinds (Info, Error). Three threads log messages concurrently while being thread-safe via a mutex.

Meyer’s Singleton Implementation 

Meyer’s singleton refers to a thread-safe implementation of the singleton design pattern in C++, introduced by Scott Meyers, a well-known C++ expert. This approach uses local static variables in a function to ensure that the Singleton instance is created only when needed and is automatically destroyed when the program ends.

Example:

Cpp

Output: 

The above code demonstrates Meyer’s singleton design pattern in which a static local variable guarantees that only one instance of the singleton is created when needed. The constructor is private and ensures class instantiation only through external. The ‘showMessage()’ method would call out operations allowed from a single instance.

Static vs Dynamic Initialization

For the initialization of the singleton instances, there are mainly two approaches when using the singleton design pattern:

  • Static initialization
  • Dynamic initialization

1. Static Initialization of Singleton

When singletons are created at starting and last until the program ends, it is Static Initialization, their lifespan is dependent on the application. This is done in C++ with the help of static variables.

Example:

Cpp

Output:

In the above code, only one instance for the singleton class will be created when the getInstance() function is called. Because we’re checking if singleton1 and singleton2 are the same instances, the code compares their locations in memory.

2. Dynamic Initialization of Singleton

The only difference in the dynamic Initialization of a singleton is that an instance is created at the time of first use for that instance, or can also be created during runtime. In general, this technique uses dynamic allocation instead of static initialization and usually requires the clean-up or deletion to be done manually if needed. Typically, managing the instance with a static pointer is used.

Example:

Cpp

Output: 

Here, Singleton:: instance a static pointer is a statically initialized nullptr, but the getInstance() method gets involved at the time of instance creation to create an instance only when needed. Mutex also provides thread safety and prevents any other running thread from creating other threads. It may include various std::unique_ptr. For automatic memory management.

Singleton Class Challenges

You can only create one instance of the application as a singleton, which leads to thread safety issues, isolation issues, or time of instance initialization issues.

  1. A singleton represents the global state, in the sense that it refers to one object instance which is referenced and used throughout the application. This makes shared instance tests difficult to isolate, which can in turn lead to affecting these tests.
  2. When even your singleton class accesses external systems (like databases, and files) the interactions would not be replaced since the singleton is tightly coupled. This creates problems for unit testing as the instance is created at the start of the program, and can not be replaced with test versions.
  3. Lastly, in the case where the Singleton is intended to be used in a multi-thread application, its thread safety while testing makes it difficult, especially during testing, the concurrent threads on the Singleton instance may lead to undesired behavior.

Best Practices for Implementing Singleton in C++

  • Private Constructor: We can make a constructor private, which will prevent the direct instantiation.
  • Thread Safety: It is protected by thread-safety features (like std::mutex or std::call_once) to avoid a race condition.
  • Memory management techniques: Memory auto cleanup by std::unique_ptr smart pointers.
  • Stop Copying: Copy constructor & assignee are deleted. So it can’t copy.

Advantages of Singleton Design Pattern

  1. Controlled Access to Single Instance: It provides controlled access to the single instance.
  2. Global point of access: this gives you a single instance of the class which can be used all over the application.
  3. Lazy Initialization: The instance is created when it is needed, which can free memory and resources in case the instance is never used.
  4. Reduced Memory Consumption:  Only a single instance of the class is created, it does not consume any redundant memory.
  5. Consistency: Ensures that the state of the object remains consistent throughout the application since all components access the same instance.

Disadvantages of Singleton Design Pattern 

  1. Global state: Singleton allows global access to all over the class, which can lead to unpredictable behaviors when multiple code pieces change their state 
  2. Unit testing: Singleton causes tight coupling, hence unit testing becomes impossible 
  3. Hidden dependencies: The presence of a singleton in the global, the code does not declare the dependencies properly, so it makes it hard to maintain the code. 
  4. Difficulty in extending: Extending or changing the singleton is difficult due to the global access of the instance. 

Conclusion

Singleton, which provides a global access point and ensures there is a single instance of a particular class. It maintains Singleton’s structure by defining the public static method, static instance, and a private constructor, Meyer’s method is considered best for implementing the Singleton pattern in C++.

FAQs

1. How is the Singleton pattern implemented in C++?

Singleton implementation by using a public static method, a static instance, and a private constructor.

2. What is the purpose of the C++ Singleton pattern?

For managing resources like configuration or logging, a singleton will ensure that only one instance is shared with the resource.

3. What does the Singleton pattern's lazy initialization mean?

Instead of creating the instance at the beginning of the program, the singleton pattern’s lazy initialization does so only when it is first required.

4. How can you make sure a C++ singleton isn't copied?

In C++, a singleton can be prevented from being copied by removing the assignment operator and copy constructor, which guarantees that the object cannot be copied.

5. Can memory leaks occur in C++ as a result of the Singleton pattern?

Yes, improper cleaning could result in memory leaks if the singleton pattern is dynamically allocated (new)

About the Author

Technical Research Analyst - Full Stack Development

Kislay is a Technical Research Analyst and Full Stack Developer with expertise in crafting Mobile applications from inception to deployment. Proficient in Android development, IOS development, HTML, CSS, JavaScript, React, Angular, MySQL, and MongoDB, he’s committed to enhancing user experiences through intuitive websites and advanced mobile applications.

Full Stack Developer Course Banner