C++ Singleton

Introduction

C++ singleton is a specially designed class of which there can only be at most one instance in the entire program. It’s commonly used the global object implementations, such as memory pool.

In this blog post, I would like to discuss the C++ singleton class implementations.

C++ Singleton

C++ Singleton Implementation Features

Because singleton class can only be constructed once and it cannot be copied and it is globally accessible, we will have the following features for the implementation.

  • The copy constructor and copy assignment are deleted.
  • The constructor is private and the class has to be constructed via a public static method.
  • The class methods, including the singleton object instantiation, have to be thread-safe.

C++ Singleton Application Example

The following application will consume the singleton class implementation from the header file.

main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "singleton.hpp"
#include <cassert>
#include <iostream>

int main()
{
// Cannot compile since the constructor is private.
// Singleton new_instance{};
std::cout << "Singleton Instance Address: " << Singleton::get_instance()
<< std::endl;
std::cout << "Singleton Instance Value: "
<< Singleton::get_instance()->get_value() << std::endl;
int const value{5};
std::cout << "Setting Singleton Instance Value To: " << value << std::endl;
Singleton::get_instance()->set_value(value);
std::cout << "Singleton Instance Value: "
<< Singleton::get_instance()->get_value() << std::endl;
assert(value == Singleton::get_instance()->get_value());
}

In the following sections, I will show a couple of singleton class implementations in a header file.

C++ 11 Implementation

The following singleton class C++ 11 implementation is thread-safe for the singleton instance instantiation and the set_value method. The get_value was designed to be thread-unsafe on purpose. But it can be implemented to be thread-safe with appropriate locks.

singleton.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#ifndef SINGLETON_HPP
#define SINGLETON_HPP

#include <memory>
#include <mutex>

std::mutex singleton_mutex;

class Singleton
{
public:
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
~Singleton() {}
// Set value has to be thread-safe.
void set_value(int value)
{
std::lock_guard<std::mutex> lock(singleton_mutex);
m_value = value;
}
// We don't want get value to be thread-safe in our case.
int get_value() const { return m_value; }
static Singleton* get_instance()
{
// Use lock to guarantee the singleton object instantiation is
// thread-safe. But lock will become a burden later on after the
// singleton object instantiation.
std::lock_guard<std::mutex> lock(singleton_mutex);
if (!instance)
{
// std::make_unique will not work
instance = std::unique_ptr<Singleton>(new Singleton());
}
return instance.get();
}

private:
static std::unique_ptr<Singleton> instance;
explicit Singleton() : m_value{0} {}
int m_value;
};

std::unique_ptr<Singleton> Singleton::instance = nullptr;

#endif // SINGLETON_HPP
1
2
3
4
5
6
$ g++ singleton.hpp main.cpp -o main -std=c++11
$ ./main
Singleton Instance Address: 0x5651733922c0
Singleton Instance Value: 0
Setting Singleton Instance Value To: 5
Singleton Instance Value: 5

There are many problems with this design and implementation. It uses a global mutex which is exposed to the user, which is not a good design in my opinion. There is a lock used in the get_instance function so that the instantiation of the singleton instance is thread-safe. However, this also means after the singleton is instantiated whenever we call get_instance there is a lock which is sometimes not desirable. For example, if we want to get the value in a thread-unsafe fashion by Singleton::get_instance()->get_value(), the lock is unnecessary. We could make a copy of the singleton instance pointer Singleton::get_instance() and use the pointer to access the get_value function to bypass the lock. But this does not seem to be a good practice either.

C++ 17 Implementation

The following singleton class C++ 17 implementation is based on the C++ 11 implementation. They are almost the same except that the static singleton pointer now can be initialized inside the class. Therefore, the C++ 17 implementation has exactly the same drawbacks as the C++ 11 implementation.

singleton.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#ifndef SINGLETON_HPP
#define SINGLETON_HPP

#include <memory>
#include <mutex>

std::mutex singleton_mutex;

class Singleton
{
public:
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
~Singleton() {}
// Set value has to be thread-safe.
void set_value(int value)
{
std::lock_guard<std::mutex> lock(singleton_mutex);
m_value = value;
}
// We don't want get value to be thread-safe in our case.
int get_value() const { return m_value; }
static Singleton* get_instance()
{
// Use lock to guarantee the singleton object instantiation is
// thread-safe. But lock will become a burden later on after the
// singleton object instantiation.
std::lock_guard<std::mutex> lock(singleton_mutex);
if (!instance)
{
// std::make_unique will not work
instance = std::unique_ptr<Singleton>(new Singleton());
}
return instance.get();
}

private:
inline static std::unique_ptr<Singleton> instance{nullptr};
explicit Singleton() : m_value{0} {}
int m_value;
};

#endif // SINGLETON_HPP
1
2
3
4
5
6
$ g++ singleton.hpp main.cpp -o main -std=c++17
$ ./main
Singleton Instance Address: 0x557c7ca482c0
Singleton Instance Value: 0
Setting Singleton Instance Value To: 5
Singleton Instance Value: 5

Scott Meyers Implementation

As we mentioned previously, the lock used in the get_instance for ensuring the thread-safe singleton instance instantiation is very annoying since it’s only useful for protecting the singleton instance instantiation but not anymore later on. Since C++ 11, the storage_duration guarantees that, “If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once (similar behavior can be obtained for arbitrary functions with std::call_once)”.

This feature can be used for ensuring the singleton instance is initialized exactly once in a multithreading environment without using a lock. Before C++ 11, there is no such guarantee and the static local variable can be initialized multiple times if multiple threads are trying to access it for the first time simultaneously. The implementation that takes the advantage of such a feature was created by Scott Meyers and it was usually referred as Meyers singleton.

singleton.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef SINGLETON_HPP
#define SINGLETON_HPP

#include <mutex>

class Singleton
{
public:
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
~Singleton() {}
// Set value has to be thread-safe.
void set_value(int value)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_value = value;
}
// We don't want get value to be thread-safe in our case.
int get_value() const { return m_value; }
static Singleton* get_instance()
{
// Static local variable initialization is thread-safe
// and will be initialized only once.
static Singleton instance{};
return &instance;
}

private:
explicit Singleton() : m_value{0} {}
std::mutex m_mutex;
int m_value;
};

#endif // SINGLETON_HPP
1
2
3
4
5
6
$ g++ singleton.hpp main.cpp -o main -std=c++11
$ ./main
Singleton Instance Address: 0x55b3d911218c
Singleton Instance Value: 0
Setting Singleton Instance Value To: 5
Singleton Instance Value: 5

Notice that because the singleton instance is a static local variable that is allocated at the beginning of the program instead of a pointer, I said singleton instance initialization instead of singleton instance instantiation. The mutex can now be a member variable of the class instead of a global variable.

Therefore, the Meyers singleton implementation looks much clever and cleaner than the other singleton implementations.

C++ Singleton VS Static Class

C++ singleton class shares lots similarities with a class whose member variables and functions are all static. I don’t know what to call for the latter class. Let’s call it “static class” for now. To some extent, the “static class” can have most of the key features that the singleton class has, including that it can only be constructed once and it cannot be copied and it is globally accessible.

The major differences I think are:

  • The singleton instance can be lazy instantiated whereas the static class is created at the beginning of the program.
  • The static variables in the static class cannot use other static variables or functions for initialization, because the order of the static variable initialization is not defined.

Final Remarks

The C++ singleton class is global so thread-safety has always to be considered in the implementation.

References

Author

Lei Mao

Posted on

02-23-2023

Updated on

02-23-2023

Licensed under


Comments