Back to C++
2026-04-068 min read

C++ Singleton Design Pattern

Learn C++ Singleton Design Pattern step by step with clear examples and exercises.

Title: C++ Singleton Design Pattern

Why This Matters

In software development, ensuring that a class has only one instance across the entire application is crucial for maintaining program integrity and avoiding unexpected behavior. The Singleton design pattern is a solution to this problem, widely used in C++ applications to create a global access point to a class while preventing multiple instances from being created. This lesson will walk you through the fundamentals of the Singleton design pattern in C++, providing practical examples, common mistakes, and practice questions to help you master it.

Prerequisites

Before diving into the Singleton design pattern, familiarize yourself with the following concepts:

  1. Object-Oriented Programming (OOP) principles in C++
  2. Static members and static functions
  3. Constructors and destructors
  4. Inheritance and polymorphism
  5. Namespaces
  6. Basic I/O operations (cin, cout), file handling (ifstream, ofstream)
  7. Time manipulation functions (ctime_s, chrono library)
  8. Exception handling (try-catch blocks)
  9. C++ Standard Template Library (STL) containers (vector, unordered_map)
  10. SQLite3 library for database operations

Core Concept

The Singleton design pattern ensures that a class has only one instance and provides a global access point to this instance. This is achieved by making the constructor of the class private and providing a static member function that returns the single instance of the class. Let's create a simple implementation of the Singleton pattern in C++:

#include <iostream>
#include <fstream>
#include <chrono>
#include <ctime>
#include <string>
#include <exception>

class Logger {
private:
static Logger* instance;
std::ofstream logFile;
Logger(const std::string& filename) : logFile(filename, std::ios_base::app) {}
Logger(const Logger&) = delete; // Prevent copy constructor
Logger& operator=(const Logger&) = delete; // Prevent assignment operator
void throwException(const std::string& message) {
throw std::runtime_error(message);
}

public:
static Logger* getInstance(const std::string& filename) {
if (!instance) {
try {
instance = new Logger(filename);
} catch (std::exception& e) {
std::cerr << "Error creating Logger instance: " << e.what() << '\n';
}
}
return instance;
}

void logMessage(const std::string& message) {
auto currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
char timeBuffer[26];
ctime_s(timeBuffer, sizeof(timeBuffer), &currentTime);

logFile << timeBuffer;
logFile << message << "\n";
}

~Logger() {
logFile.close();
}
};

// Initialize static member 'instance'
Logger* Logger::instance = nullptr;

int main() {
try {
// Get the single instance of Logger and log messages
Logger* logger1 = Logger::getInstance("log.txt");
logger1->logMessage("Starting application...");

// Create another instance (should return the same object)
Logger* logger2 = Logger::getInstance("log.txt");

// Log additional messages using both instances
logger1->logMessage("Performing some tasks...");
logger2->logMessage("More tasks being executed...");
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << '\n';
return 1;
}

return 0;
}

In this example, we define a Logger class that logs messages to a file. The constructor takes a filename as an argument and opens the specified file for appending. Inside the Logger class, we define a static member function getInstance() to manage the single instance of the Logger class. The logMessage() function writes log messages to the file in a formatted manner, including the current time. We also include exception handling within the getInstance() function to handle cases where the file cannot be opened for writing or other unexpected errors occur.

Worked Example

Now let's create a more practical example using the Singleton pattern: a DatabaseConnectionManager class that manages database connections for multiple SQL queries. The DatabaseConnectionManager class should have a single instance, ensuring that all database connections are properly managed and reused when possible.

#include <iostream>
#include <sqlite3.h>
#include <string>
#include <stdexcept>
#include <unordered_map>
#include <mutex>

class DatabaseConnectionManager {
private:
static DatabaseConnectionManager* instance;
sqlite3* db;
std::unordered_map<std::string, sqlite3_stmt*> statements;
std::mutex mutex;

DatabaseConnectionManager(const std::string& databaseName) {
if (sqlite3_open(databaseName.c_str(), &db)) {
throw std::runtime_error("Can't open database: " + sqlite3_errmsg(db));
}
}

DatabaseConnectionManager(const DatabaseConnectionManager&) = delete; // Prevent copy constructor
DatabaseConnectionManager& operator=(const DatabaseConnectionManager&) = delete; // Prevent assignment operator

public:
static DatabaseConnectionManager* getInstance(const std::string& databaseName) {
if (!instance) {
std::unique_lock<std::mutex> lock(mutex);
if (!instance) {
instance = new DatabaseConnectionManager(databaseName);
}
}
return instance;
}

sqlite3_stmt* prepare(const std::string& sql) {
std::unique_lock<std::mutex> lock(mutex);
auto it = statements.find(sql);
if (it != statements.end()) {
return it->second;
}

sqlite3_prepare_v2(db, sql.c_str(), -1, &statements[sql], nullptr);
return statements[sql];
}

void executeStep(sqlite3_stmt* stmt) {
int rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
throw std::runtime_error("Error executing statement: " + sqlite3_errmsg(db));
}
}

void finalizeStatement(sqlite3_stmt* stmt) {
sqlite3_finalize(stmt);
statements.erase(stmt);
}

~DatabaseConnectionManager() {
sqlite3_close(db);
}
};

// Initialize static member 'instance'
DatabaseConnectionManager* DatabaseConnectionManager::instance = nullptr;

int main() {
try {
// Get the single instance of DatabaseConnectionManager and execute SQL queries
DatabaseConnectionManager* dbManager = DatabaseConnectionManager::getInstance("test.db");

sqlite3_stmt* stmt1 = dbManager->prepare("SELECT * FROM users WHERE id = 1");
dbManager->executeStep(stmt1);

sqlite3_stmt* stmt2 = dbManager->prepare("INSERT INTO users (name, age) VALUES ('John Doe', 30)");
dbManager->executeStep(stmt2);

// Reuse prepared statement for efficiency
dbManager->finalizeStatement(stmt1);
sqlite3_stmt* stmt1 = dbManager->prepare("SELECT * FROM users WHERE id = 1");
dbManager->executeStep(stmt1);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << '\n';
return 1;
}

return 0;
}

In this example, we create a DatabaseConnectionManager class that manages database connections for multiple SQL queries. The constructor takes a database name as an argument and opens the specified database. Inside the DatabaseConnectionManager class, we define a static member function getInstance() to manage the single instance of the DatabaseConnectionManager class. The prepare() function prepares an SQL statement for execution, while the executeStep() function executes a step of the prepared statement and checks for errors. The finalizeStatement() function finalizes a prepared statement and removes it from the internal map for reuse. We also use a mutex to ensure thread safety when accessing the instance and its methods in a multi-threaded environment.

Common Mistakes

  1. Incorrect implementation of private constructor and destructor: If the constructor or destructor is not marked as private, other classes can create instances of the Singleton class, breaking the pattern's purpose.
  2. Lack of static member function for accessing the instance: Without a static member function like getInstance(), there would be no global access point to the single instance of the class.
  3. Missing deletion of the instance in destructor: If the instance is not deleted in the destructor, memory leaks may occur when the Singleton object outlives other objects in the program.
  4. Incorrect handling of multiple threads: In a multi-threaded environment, it's essential to ensure that only one instance of the Singleton class is created across all threads. This can be achieved by using synchronization mechanisms such as locks or atomic variables.
  5. Lack of proper error checking and exception handling: Proper error checking and exception handling should be implemented in the getInstance() function to handle cases where the file cannot be opened for writing or other unexpected errors occur.
  6. Not reusing prepared statements for efficiency: In database-related Singleton implementations, it's important to reuse prepared statements when possible to improve performance and reduce resource usage.
  7. Not closing database connections properly: Failing to close database connections can lead to resource leaks and may affect the overall performance of the application.
  8. Not using thread-safe synchronization mechanisms: In a multi-threaded environment, it's crucial to use thread-safe synchronization mechanisms like mutexes or atomic variables to ensure that only one instance of the Singleton class is created and accessed safely by all threads.
  9. Not properly handling exceptions during construction: If an exception occurs during construction of the Singleton instance, it should be caught and handled appropriately to prevent the program from crashing. In this case, you might want to return a null pointer or create a new instance with default values if necessary.
  10. Not properly managing resources in destructor: The destructor should properly clean up any allocated resources, such as file handles, database connections, or network sockets, to prevent memory leaks and other resource-related issues.

Practice Questions

  1. Modify the Logger example to log messages with different severity levels (e.g., INFO, WARNING, ERROR). Implement a method to set the current logging level and filter out messages based on their severity.
  2. Create a Singleton class for managing a database connection in C++. The class should have methods for opening, closing, and executing SQL queries against the database.
  3. Implement the Singleton pattern using inheritance. Create a base class with a private constructor and a static function to return the single instance of the derived class.
  4. Modify the Logger example to include timestamp formatting options (e.g., YYYY-MM-DD HH:MM:SS). Allow users to set the desired timestamp format using a configuration file or command line arguments.
  5. Implement a Singleton class for managing a cache of frequently accessed data in C++. The cache should use an LRU (Least Recently Used) eviction strategy to remove items when memory usage exceeds a certain threshold.
  6. Create a Singleton class for handling network connections in C++. The class should manage multiple sockets and provide methods for sending and receiving data over the network.
  7. Implement a Singleton class for managing a logging subsystem that supports multiple loggers (e.g., console, file) in C++. The class should allow users to configure which loggers are enabled or disabled at runtime.
  8. Modify the DatabaseConnectionManager example to support transactions and rollbacks. Add methods for starting and committing transactions, as well as rolling back changes when necessary.
  9. Implement a Singleton class for managing a pool of database connections in C++. The class should maintain a pool of pre-allocated connections, reusing them whenever possible to improve performance.
  10. Create a Singleton class for handling file operations in C++. The class should manage multiple files and provide methods for reading, writing, appending, and deleting files.

FAQ

  1. Why is the Singleton pattern useful? The Singleton pattern ensures that a class has only one instance across the entire application, which can be beneficial for managing global resources such as database connections, loggers, and other objects with state that should not be shared or duplicated.
  2. Can I implement the Singleton pattern using templates? Yes, it is possible to create a template-based Singleton implementation in C++. This allows you to write a single Singleton class that can be
C++ Singleton Design Pattern | C++ | XQA Learn