【问题标题】:Convert void* to std::function<void()>将 void* 转换为 std::function<void()>
【发布时间】:2012-12-01 20:28:41
【问题描述】:

将不同参数的函数指针存储在一个空指针向量中。

unordered_map<string, vector<void*> > List;

template <typename T>
void Listen(string Name, function<void(T)> Function)
{
    List[Name].push_back(&Function);
}

然后我想给他们打电话,假设TFire 的类型相同,与Listen 使用的类型相同。

template <typename T>
void Fire(string Name, T Data)
{
    auto Functions = List[Name];

    for (auto i = Functions.begin(); i != Functions.end(); ++i)
    {
        (function<void(T)>)i)(Data);
    }
}

但我得到一个编译器错误,显示为error C2064: term does not evaluate to a function taking 1 arguments in file ...\vc\include\xrefwrap 431 1

我做错了什么?

【问题讨论】:

  • 你正在推送一个参数的地址。
  • 为什么不将std::function 保存在List 中?
  • @Lol4t0。可悲的是,我无法在同一个列表中保存不同的类型。比如function&lt;void(int)&gt;function&lt;void(class*)&gt;function&lt;void()&gt;都是不同的类型。
  • @chill。既然它是一个空指针向量,那有什么问题?
  • 是的,但是在调用它们时你将无法确定函数的类型

标签: c++ function pointers casting std


【解决方案1】:

首先,您在此处获取参数的地址:

List[Name].push_back(&Function);

然后您尝试将迭代器对象转换为 std::function 对象:

(function<void(T)>)i)

你想做的事,可以这样,虽然不好看,委婉地说:

unordered_map<string, vector<void*> > List;

template <typename T>
void Listen(string Name, function<void(T)> &Function)
{
    List[Name].push_back(&Function);
}

template <typename T>
void Fire(string Name, T Data)
{
    auto Functions = List[Name];

    for (auto i = Functions.begin(); i != Functions.end(); ++i)
    {
      function<void(T)> *ptr = *i;

      (*ptr) (Data);
    }
}

它可能会以多种方式中断,例如,您无法控制以 Listen 中的某个名称注册的函数在 Fire 中使用正确的参数调用 - 考虑调用 Listen&lt;int&gt; ("foo", f); 然后执行 @987654328 @

另一种方法 - 只为回调传递闭包:

unordered_map<string, std::vector<function<void()> > > List;

void Listen(string Name, function<void()> Function)
{
    List[Name].push_back(Function);
}

void Fire(string Name)
{
    auto Functions = List[Name];

    for (auto i = Functions.begin(); i != Functions.end(); ++i)
      (*i) ();
}

【讨论】:

  • &amp;为什么用在Listen函数的参数上?我能以某种方式避免这种情况吗?
  • 这是确保函数使用左值调用所必需的,即使用具有地址的东西。只要地址在矢量/地图中,仍然必须确保该地址保持有效。
【解决方案2】:
#include <functional>
#include <unordered_map>
#include <memory>
#include <string>
#include <vector>

template<typename T> struct BlockDeduction{typedef T type;};
struct BaseCallback {
  virtual ~BaseCallback();
  template<typename T>
  void DoCall( typename BlockDeduction<T>::type&& t ) const;
};
template<typename T>
struct Callback: BaseCallback
{
  std::function<void(T)> func;
  Callback( std::function<void(T)> const& f ):func(f) {}
};


template<typename T>
void BaseCallback::DoCall( typename BlockDeduction<T>::type&& t ) const {
  Assert( dynamic_cast<Callback<T>const*>(this) );
  static_cast<Callback<T>const*>(this).func(std::forward(t));
}

typedef std::unique_ptr<BaseCallback> upCallback;
template<typename T>
upCallback make_callback( std::function<void(T)> const& f ) {
  return upCallback( new Callback<T>( f ) );
}


struct Listener {
  std::unordered_map< std::string, std::vector<upCallback>> List;
  template<typename T>
  void Listen( std::string Name, std::function<void(T)> f) {
    List[Name].push_back( make_callback(f) );
  }
  template<typename T>
  void Fire( std::string Name, typename BlockDeduction<T>::type&& t ) {
    auto callbacks = List.find(Name);
    if (callbacks == List.end()) return;
    for(auto it = callbacks->second.begin(); it != callbacks->second.end(); ++it) {
      if (it +1 = callbacks->second.end())
      {
        (**it).DoCall<T>( std::forward(t) );
      } else {
        (**it).DoCall<T>( t );
      }
    }
  }
};

...或类似的东西。

这会将std::function 的副本存储在地图中,通常是包裹起来的。内存通过unique_ptr 处理。我小心地阻止了类型推导,其中类型必须与您安装 Listener 时使用的类型完全相同(此时的自动类型推导相当脆弱)。

在调试中,如果您违反名称类型映射,您将获得断言失败。

需要为 nullary 回调做一些额外的工作。只需编写一个DoCallBaseCallback 转换为Callback&lt;void&gt;,将Callback&lt;void&gt; 特化为空值function 包装器,将make_callback 特化为空值function,然后为Listener 编写一个Fire(string) 方法调用裸露的DoCall

或者创建一个struct Empty 并使用 lambdas 在function&lt;void(Empty)&gt; 中包装空函数,这将涉及的代码略少,但在运行时会更慢。

【讨论】:

  • 我喜欢这样,但我需要一些时间才能真正理解你所做的一切。
  • 请注意,std::forward 废话可能是错误的——我仍在尝试理解它。我可能不应该包含它,只是做了一些T 类型的冗余副本。 BlockDeduction 仅用于阻止自动函数类型推断(因为 Fire 采用的 T 必须与 T Listen 采用的类型完全相同,或者未定义的行为结果)。 BaseCallback 确实存在只是为了拥有一个虚拟 dtor(因此调用它的子类 dtor),而 DoCall 方法只是将危险的 static_cast 放入类中。 unique_ptr 管理生命周期。
猜你喜欢
  • 1970-01-01
  • 2014-12-11
  • 2017-07-08
  • 2018-02-26
  • 2019-06-05
  • 2011-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多