在 C++ 中,我可以使用来自不同文件的值安全地初始化 unordered_map 吗?
In C++, can I safely initialize an unordered_map with values from different files?
想象一下这样的代码:
std::unordered_map<std::string, std::function<Foo *()>> FooFactory;
void registerFoo(std::string name, std::function<Foo *()> factory)
{
FooFactory.emplace(name, factory);
}
如果我现在要在另一个文件中编写这样的代码:
static bool Baz = [](){ registerFoo("baz", []() { return new BazFoo(); })}();
还有一个:
static bool Bar = [](){ registerFoo("bar", []() { return new BarFoo(); })}();
在这种情况下,registerFoo 在程序初始化时被调用,但 FooFactory 随后被清零,因此注册函数消失。
有没有办法以安全、独立于编译器的方式(对于 c++14)使它工作?
您可以将工厂本身粘在一个函数中:
std::unordered_map<std::string, std::function<Foo *()>>& getFactory() {
static std::unordered_map<std::string, std::function<Foo *()>> FooFactory;
return FooFactory;
}
您的注册功能可以通过:
void registerFoo(std::string name, std::function<Foo *()> factory)
{
getFactory().emplace(name, factory);
}
这应该可以保证顺序。
为避免静态初始化顺序失败,您可以将工厂访问包装到函数调用中,在第一次调用时构造它:
using
t_NameToFactoryMap = std::unordered_map<std::string, std::function<Foo *()>>
t_NameToFactoryMap * p_foo_factory{}; // initialized with nullptr before dynamic initialization starts
t_NameToFactoryMap &
getFooFactory(void)
{
if(!p_foo_factory)
{
p_foo_factory = new t_NameToFactoryMap{};
}
return(*p_foo_factory);
}
void registerFoo(std::string name, std::function<Foo *()> factory)
{
getFooFactory().emplace(name, factory);
}
这种自动注册方法的缺点是,如果您决定在某些静态库项目中使用它们,它们会产生问题。使用此静态库的项目不会引用 Baz
或 Bar
除非它们 link 此静态库使用一些依赖于编译器的标志,例如 gcc 的 --whole-archive
。
因此,更好的解决方案是在 main
下显式创建工厂并注册所有必需的项目,而根本不处理动态初始化。
虽然不鼓励使用这样的全局上下文,但除了@Barry 的回答之外,还有一些您还应该考虑的项目:
- 你应该用
mutex
保护 emplace
(以防多个线程试图添加到 unordered_map
)
- 可选 return emplace 的成功参数(由
second
访问)。
- 转发您的论据,确保完美转发:
bool registerFoo(std::string &&name, std::function<Foo *()> &&factory)
{
static std::mutex register_mutex;
std::lock_guard<std::mutex> lock(register_mutex);
return getFactory().emplace(
std::forward<std::string>(name),
std::forward<std::std::function<Foo *()>>(factory)
).second;
}
然后:
static bool Baz = [](){ return registerFoo("baz", []() { return new BazFoo(); })}();
当您不再需要这些空闲函数指针时,不要忘记创建一个工具来删除它们。
首先,你需要一些线程安全:
template<class T, class M=std::shared_timed_mutex> // shared_mutex in C++17
struct mutex_guarded {
template<class F>
auto write( F&& f )
->std::decay_t<std::result_of_t<F(T&)>> {
auto l = lock();
return std::forward<F>(f)(t);
}
template<class F>
auto read( F&& f ) const
->std::decay_t<std::result_of_t<F(T const&)>> {
auto l = lock();
return std::forward<F>(f)(t);
}
mutex_guarded() {}
template<class T0, class...Ts,
std::enable_if_t<!std::is_same<std::decay_t<T0>, mutex_guarded>{},int> =0
>
mutex_guarded(T0&& t0, Ts&&...ts):
t(std::forward<T0>(t0), std::forward<Ts>(ts)...)
{}
mutex_guarded( mutex_guarded const& o ):
t(o.copy_from())
{}
mutex_guarded( mutex_guarded && o ):
t(o.move_from())
{}
mutex_guarded& operator=(mutex_guarded const&)=delete;
mutex_guarded& operator=(mutex_guarded &&)=delete;
mutex_guarded& operator=(T const& t) {
write([&t](T& dest){dest=t;});
return *this;
}
mutex_guarded& operator=(T&& t) {
write([&t](T& dest){dest=std::move(t);});
return *this;
}
private:
T copy_from() const& { return read( [](T const& t){ return t; } ); }
T copy_from() && { return move_from(); }
T move_from() { return write( [](T& t){ return std::move(t); } ); }
std::unique_lock<M> lock() const {
return std::unique_lock<M>(m);
}
std::shared_lock<M> lock() {
return std::shared_lock<M>(m);
}
M m; // mutex
T t;
};
这让我们有一个:
using foo_factory = std::function<std::unique_ptr<Foo>()>;
using foo_factories = std::unordered_map<std::string, foo_factory>;
mutex_guarded<foo_factories>& get_foo_factories() {
static mutex_guarded<foo_factories> map;
return map;
}
其中有线程安全初始化,然后
void registerFoo(std::string name, std::function<Foo *()> factory)
{
get_foo_factories().write([](auto& f){f.emplace(name, factory);});
}
是线程安全的并且保证足够早地初始化工厂。
在关闭时,工厂被摧毁的时间是你无法控制的(相反的建造顺序),而且可能为时过早。
mutex_guarded<foo_factories>*& get_foo_factories() {
static auto* map = new mutex_guarded<foo_factories>;
return map;
}
void registerFoo(std::string name, std::function<Foo *()> factory)
{
get_foo_factories()->write([](auto& f){f.emplace(name, factory);});
}
void end_foo_factories() {
auto*& ptr = get_foo_factories();
delete ptr; ptr = nullptr;
}
这将把它放在堆上,在那里它会活得更久。请注意,这也会泄漏工厂和地图;可以添加手动销毁"late enough"。请注意,这种破坏不是线程安全的,也不能廉价地使其成为线程安全的;它应该在清理完所有线程后发生。
想象一下这样的代码:
std::unordered_map<std::string, std::function<Foo *()>> FooFactory;
void registerFoo(std::string name, std::function<Foo *()> factory)
{
FooFactory.emplace(name, factory);
}
如果我现在要在另一个文件中编写这样的代码:
static bool Baz = [](){ registerFoo("baz", []() { return new BazFoo(); })}();
还有一个:
static bool Bar = [](){ registerFoo("bar", []() { return new BarFoo(); })}();
在这种情况下,registerFoo 在程序初始化时被调用,但 FooFactory 随后被清零,因此注册函数消失。
有没有办法以安全、独立于编译器的方式(对于 c++14)使它工作?
您可以将工厂本身粘在一个函数中:
std::unordered_map<std::string, std::function<Foo *()>>& getFactory() {
static std::unordered_map<std::string, std::function<Foo *()>> FooFactory;
return FooFactory;
}
您的注册功能可以通过:
void registerFoo(std::string name, std::function<Foo *()> factory)
{
getFactory().emplace(name, factory);
}
这应该可以保证顺序。
为避免静态初始化顺序失败,您可以将工厂访问包装到函数调用中,在第一次调用时构造它:
using
t_NameToFactoryMap = std::unordered_map<std::string, std::function<Foo *()>>
t_NameToFactoryMap * p_foo_factory{}; // initialized with nullptr before dynamic initialization starts
t_NameToFactoryMap &
getFooFactory(void)
{
if(!p_foo_factory)
{
p_foo_factory = new t_NameToFactoryMap{};
}
return(*p_foo_factory);
}
void registerFoo(std::string name, std::function<Foo *()> factory)
{
getFooFactory().emplace(name, factory);
}
这种自动注册方法的缺点是,如果您决定在某些静态库项目中使用它们,它们会产生问题。使用此静态库的项目不会引用 Baz
或 Bar
除非它们 link 此静态库使用一些依赖于编译器的标志,例如 gcc 的 --whole-archive
。
因此,更好的解决方案是在 main
下显式创建工厂并注册所有必需的项目,而根本不处理动态初始化。
虽然不鼓励使用这样的全局上下文,但除了@Barry 的回答之外,还有一些您还应该考虑的项目:
- 你应该用
mutex
保护emplace
(以防多个线程试图添加到unordered_map
) - 可选 return emplace 的成功参数(由
second
访问)。 - 转发您的论据,确保完美转发:
bool registerFoo(std::string &&name, std::function<Foo *()> &&factory)
{
static std::mutex register_mutex;
std::lock_guard<std::mutex> lock(register_mutex);
return getFactory().emplace(
std::forward<std::string>(name),
std::forward<std::std::function<Foo *()>>(factory)
).second;
}
然后:
static bool Baz = [](){ return registerFoo("baz", []() { return new BazFoo(); })}();
当您不再需要这些空闲函数指针时,不要忘记创建一个工具来删除它们。
首先,你需要一些线程安全:
template<class T, class M=std::shared_timed_mutex> // shared_mutex in C++17
struct mutex_guarded {
template<class F>
auto write( F&& f )
->std::decay_t<std::result_of_t<F(T&)>> {
auto l = lock();
return std::forward<F>(f)(t);
}
template<class F>
auto read( F&& f ) const
->std::decay_t<std::result_of_t<F(T const&)>> {
auto l = lock();
return std::forward<F>(f)(t);
}
mutex_guarded() {}
template<class T0, class...Ts,
std::enable_if_t<!std::is_same<std::decay_t<T0>, mutex_guarded>{},int> =0
>
mutex_guarded(T0&& t0, Ts&&...ts):
t(std::forward<T0>(t0), std::forward<Ts>(ts)...)
{}
mutex_guarded( mutex_guarded const& o ):
t(o.copy_from())
{}
mutex_guarded( mutex_guarded && o ):
t(o.move_from())
{}
mutex_guarded& operator=(mutex_guarded const&)=delete;
mutex_guarded& operator=(mutex_guarded &&)=delete;
mutex_guarded& operator=(T const& t) {
write([&t](T& dest){dest=t;});
return *this;
}
mutex_guarded& operator=(T&& t) {
write([&t](T& dest){dest=std::move(t);});
return *this;
}
private:
T copy_from() const& { return read( [](T const& t){ return t; } ); }
T copy_from() && { return move_from(); }
T move_from() { return write( [](T& t){ return std::move(t); } ); }
std::unique_lock<M> lock() const {
return std::unique_lock<M>(m);
}
std::shared_lock<M> lock() {
return std::shared_lock<M>(m);
}
M m; // mutex
T t;
};
这让我们有一个:
using foo_factory = std::function<std::unique_ptr<Foo>()>;
using foo_factories = std::unordered_map<std::string, foo_factory>;
mutex_guarded<foo_factories>& get_foo_factories() {
static mutex_guarded<foo_factories> map;
return map;
}
其中有线程安全初始化,然后
void registerFoo(std::string name, std::function<Foo *()> factory)
{
get_foo_factories().write([](auto& f){f.emplace(name, factory);});
}
是线程安全的并且保证足够早地初始化工厂。
在关闭时,工厂被摧毁的时间是你无法控制的(相反的建造顺序),而且可能为时过早。
mutex_guarded<foo_factories>*& get_foo_factories() {
static auto* map = new mutex_guarded<foo_factories>;
return map;
}
void registerFoo(std::string name, std::function<Foo *()> factory)
{
get_foo_factories()->write([](auto& f){f.emplace(name, factory);});
}
void end_foo_factories() {
auto*& ptr = get_foo_factories();
delete ptr; ptr = nullptr;
}
这将把它放在堆上,在那里它会活得更久。请注意,这也会泄漏工厂和地图;可以添加手动销毁"late enough"。请注意,这种破坏不是线程安全的,也不能廉价地使其成为线程安全的;它应该在清理完所有线程后发生。