Reputation: 97
I was wondering if it is possible to ensure that a function is only called during the static initialization step of a program?
As an example, say that I have some singleton class that contains a std::map
object and exposes the insert
and at
methods of it. I would like to ensure that reading data from it (the at
method) is thread-safe which, to my understanding, requires ensuring that nothing is modifying the data (i.e. using the insert
method).
The map is intended to be filled only during static initialization, at which time I'm assuming there is only one thread. Is there any way that I can ensure no misguided user calls insert
once main()
has begun?
Example code
#include <map>
#include <string>
class Singleton {
private:
std::map<std::string, std::string> m_map;
public:
static Singleton& instance() {
static Singleton theSingleton;
return theSingleton;
}
static bool insert(const std::string& key, const std::string& value) {
return instance().m_map.insert(std::make_pair(key, value) ).second;
}
static std::string at(const std::string& key) {
return instance().m_map.at(key);
}
};
static bool inserted = Singleton::insert("Hello", "World"); // fine
bool addItem(const std::string& key, const std::string& value) {
return Singleton::insert(key, value); // not OK
}
Needless(?) to say the actual code is a good deal more complex than this simple example.
Edit after solution: It looks like the best way to make this as safe as possible is to maintain a status
variable that records whether the singleton is in 'insert' or 'read' mode and act accordingly. Thanks to all for their ideas and suggestions!
Upvotes: 7
Views: 315
Reputation: 18081
Just like Jürgen with the non-Java way but c/c++ way of creating a singleton (ie a namespace).
Why doing this way:
this
to access the state;singleton.hpp
namespace singleton{
void lock();
bool instert(const std::string& key, const std::string& value);
std::string at(const std::string& key)
}
singleton.cpp
namespace singleton{
namespace{
inline decltype(auto)
get_map(){
static std::map<std::string, std::string> m_map;
return m_map;
}
bool m_locked=false; //OK guarenteed to happen before any dynamic initialization [basic.start.static]
}
void lock() {
m_locked = true;
}
bool insert(const std::string& key, const std::string& value) {
if (m_locked) { return false; }
return get_map().insert(std::make_pair(key, value)).second;
}
std::string at(const std::string& key) {
return get_map().at(key);
}
}
Also if the singleton must be used in a generic code a struct can be used for that:
struct singleton_type{
static void lock() {singleton::lock();}
static auto insert(const std::string& key, const std::string& value) {
return singleton::insert(key,value);
}
static auto at(const std::string& key) {
return singleton::at(key,value);
}
};
auto x = singleton_type{};
auto y = singleton_type{};
// x and y refers to the same and unique object file!!!
Tomorrow, stop coding in java :).
Upvotes: 1
Reputation: 16424
If you can guarantee that user won't read the map before main()
in the initialization stage, one solution is to construct a static map only for initialization, then move it to the singleton when the singleton is being constructed.
Since the construction happens the first time instance()
is called, you can be sure that map is correctly initialized.
Then other call to insert
won't have effect on the singleton. You can also add mutex to protect insert
to avoid UB from race condition.
class Singleton {
private:
std::map<std::string, std::string> m_map;
static auto& init_map() {
static std::map<std::string, std::string> m;
return m;
}
Singleton() {
m_map = std::move(init_map());
init_map().clear();
}
public:
static Singleton& instance() {
static Singleton theSingleton;
return theSingleton;
}
static bool insert(const std::string& key, const std::string& value) {
// you can also add mutex to protect here,
// because calling insert from different threads without
// protection will screw up its internal state, even if
// the init_map becomes useless after main
return init_map().insert(std::make_pair(key, value) ).second;
}
static std::string at(const std::string& key) {
return instance().m_map.at(key);
}
};
Upvotes: 2
Reputation: 76
I guess you also want to use the 'at' method in setting up your application. Why not add a 'lock' method and call that simple as first function in the main?
#include <map>
#include <string>
class Singleton {
private:
std::map<std::string, std::string> m_map;
bool m_locked;
Singleton() : m_locked(false) { }
public:
static Singleton& instance() {
static Singleton theSingleton;
return theSingleton;
}
static void lock() {
instance().m_locked = true;
}
static bool insert(const std::string& key, const std::string& value) {
if (instance().m_locked) { return false; }
return instance().m_map.insert(std::make_pair(key, value)).second;
}
static std::string at(const std::string& key) {
return instance().m_map.at(key);
}
};
static bool inserted = Singleton::insert("Hello", "World"); // fine
bool addItem(const std::string& key, const std::string& value) {
return Singleton::insert(key, value); // not OK
}
int main(int argc, char** argv)
{
Singleton::lock();
Singleton::insert("Hello2", "World2"); // fails
return 0;
}
Upvotes: 4