инициализация — В C ++ я могу безопасно инициализировать unordered_map со значениями из разных файлов?

Представьте себе код такой:

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)?

3

Решение

Вы можете вставить саму фабрику в функцию:

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);
}

Это должно гарантировать порядок.

10

Другие решения

Хотя использование глобального контекста, подобного этому, не рекомендуется, вот еще несколько вещей, которые вы должны рассмотреть в дополнение к ответу @ Barry:

  1. Вы должны охранять emplace с mutex (в случае, если несколько потоков пытаются добавить к unordered_map)
  2. При желании вернуть аргумент успеха emplace (доступный second).
  3. Пересылайте ваши аргументы, чтобы обеспечить идеальную пересылку:
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(); })}();

Не забудьте создать средство для удаления всех этих бесплатных указателей на функции, когда они вам больше не нужны.

1

Во-первых, вам нужна безопасность потоков:

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;
}

это поместит его в кучу, где он будет жить немного дольше. Обратите внимание, что это также утечка и фабрики и карты; ручное уничтожение «достаточно поздно» может быть добавлено. Обратите внимание, что это уничтожение не является потокобезопасным, и при этом его нельзя дешево сделать потокобезопасным; это должно произойти после того, как все потоки были очищены.

1

Чтобы избежать статического сбоя порядка инициализации, вы можете заключить фабричный доступ в вызов функции, создавая его во время первого вызова:

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 если они не связывают эту статическую библиотеку, используя какой-либо зависимый от компилятора флаг, например --whole-archive для GCC.

Поэтому лучшим решением было бы явно создать фабрику ниже main и зарегистрируйте все необходимые элементы, не имея дело с динамической инициализацией вообще.

0
По вопросам рекламы [email protected]