我想知道是否有可能确保仅在程序的静态初始化步骤中调用函数?
作为一个例子,假设我有一些单例类,其中包含一个std::map
对象,并公开了该对象的insert
和at
方法。我想确保从其中读取数据(at
方法)是线程安全的,据我所知,这需要确保没有任何内容在修改数据(即,使用insert
方法)。 / p>
该映射旨在在静态初始化期间仅 填充,此时我假设只有一个线程。 insert
开始后,有什么方法可以确保用户不会误导main()
?
示例代码
#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
}
不需要(?)来说明实际代码比此简单示例要复杂得多。
在解决方案之后进行编辑:看来,使其尽可能安全的最佳方法是维护一个status
变量,该变量记录单例是“插入”还是“读取”模式并采取相应行动。感谢所有人的想法和建议!
答案 0 :(得分:4)
我想您也想在设置应用程序时使用“ at”方法。 为什么不添加一个'lock'方法并在主函数中调用第一个函数那样简单呢?
memmap
答案 1 :(得分:2)
如果可以保证在初始化阶段用户不会在main()
之前读取地图,则一种解决方案是构造一个仅用于初始化的静态地图,然后在构造单例时将其移至单例。
由于构造是在第一次调用instance()
时发生的,因此可以确保正确初始化了地图。
然后其他对insert
的调用不会影响单例。您还可以添加互斥量来保护insert
,以防止UB出现竞争状况。
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);
}
};
答案 2 :(得分:1)
就像Jürgen一样,使用非Java方式,但是使用c / c ++方式创建单例(即名称空间)。
为什么要这样做:
this
来访问状态,因此在链接时效率更高并且更容易优化; 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);
}
}
此外,如果必须在通用代码中使用单例,则可以为此使用结构:
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!!!
明天,停止使用Java进行编码:)。