The Usual Procedure
A singleton is a class that can be instantiated only once. In most cases, global access is desirable. A typical use case is a logger class, that can be accessed from everywhere and is routing all messages to a single data sink. To implement a singleton in C++ is quit easy. The static
attribute comes to help. A factory()
method and a private
constructor is all we need:
class singleton
{
public:
static singleton* factory()
{
static singleton s;
return &s;
}
private:
singleton()
{}
~singleton()
{}
};
The only way to get the singleton is the factory method. And it's guaranteed that these methods return the one and only instance every time. This is good enough for most cases. (Note that the factory method is not threadsafe.)
Handle Process Signals
I came across another use case for singletons: The implementation of a signal handler. So I decided to push this topic a little harder. When implementing a signal handler, it's not important to have a single instance of a class all the time. It's important to have a static
method that can be called from the system and a static
object (the singleton) that does some work with this signal. After that, the singleton can be removed. But how can we get rid of the singleton? In the example above, the singleton constructor comes in use with the first call of the factory method. Thereafter, the singleton remains until the program terminates. Even after the return from the main function, it still exists. Only the runtime environment has real control about the lifetime of static
objects. To overcome this restriction, a static
pointer is very handy. And it makes sense to use a std::unique_ptr<> to retain ownership. Let's try this. First, we define a global method to catch process signals:
void handle_signal(int sig);
Second, we define a class to catch and process signals (demo code for a linux system, untested):
struct signal_handler
{
std::promise< int > result_;
signal_handler()
: result_()
{ std::signal(SIGINT, &handle_signal);
}
~signal_handler()
{ std::signal(SIGINT, SIG_DFL);
}
int wait()
{
auto f = result_.get_future();
return f.get();
}
void send_signal(int sig)
{
result_.set_value(sig);
}
};
So far, everything is fine. But the code is still useless. We need a manager to define a static
(unique) pointer and to control its lifetime:
struct signal_mgr
{
signal_mgr()
{
BOOST_ASSERT_MSG(!impl_, "signal handler already installed");
impl_.reset(new signal_handler());
}
virtual ~signal_mgr()
{
impl_.release();
}
int wait()
{
return impl_->wait();
}
static std::unique_ptr< signal_handler > impl_;
};
std::unique_ptr< signal_handler > signal_mgr ::impl_;
Now, we can fill up the handler
function:
void handle_signal(int sig)
{
BOOST_ASSERT_MSG(signal_mgr ::impl_, "signal handler not initialized");
if (signal_mgr ::impl_)
{
signal_mgr ::impl_->send_signal(sig);
}
}
And now, it's utterly simple to install a signal handler and to wait:
signal_mgr signal;
signal.wait();
You can find a complete implementation in the download section and here.
Conclusion
It makes sense to use a static std::unique_ptr<>
to implement a temporary singleton for the following reasons:
- Automatic management of lifetime
explicit operator bool() const
available - Retains sole ownership
- Providing exception safety
History
- 2015-05-24: Initial release