3

想象一下这样的代码:

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

4

4 回答 4

10

您可以将工厂本身粘贴在函数中:

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

这应该保证订购。

于 2017-08-30T18:36:19.720 回答
1

尽管不鼓励使用这样的全局上下文,但除了@Barry 的回答之外,您还应该考虑以下一些事项:

  1. 你应该emplace用 a保护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(); })}();

当您不再需要它们时,不要忘记创建一个工具来删除所有这些自由函数指针。

于 2017-08-30T19:15:46.183 回答
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;
}

这将把它放在堆上它会活得更久。请注意,这也会泄露工厂和地图;可以添加“足够晚”的手动销毁。请注意,这种销毁不是线程安全的,也不能廉价地使其成为线程安全的;它应该在所有线程都被清理之后发生。

于 2017-08-30T21:18:38.960 回答
0

为避免静态初始化顺序惨败,您可以在第一次调用期间将工厂访问封装到构造它的函数调用中:

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-archivegcc。

因此,更好的解决方案是在下面显式创建工厂main并注册所有必需的项目,而根本不处理动态初始化。

于 2017-08-30T18:41:13.283 回答