将lambda表达式作为c ++中的成员函数指针传递

时间:2012-08-10 11:51:57

标签: c++ lambda c++11 function-pointers

我有一个框架函数,它需要一个对象和一个成员函数指针(回调),如下所示:

do_some_work(Object* optr, void (Object::*fptr)()); // will call (optr->*fptr)()

如何将lambda表达式传递给它?想做这样的事:

class MyObject : public Object
{
    void mystuff()
    {
        do_some_work(this, [](){ /* this lambda I want to pass */ });
    }
};

这一切的意思是不要使用回调来混淆MyObject类的接口。

UPD 我无法改进do_some_work,因为我不控制框架,因为实际上它不是一个函数,它有数百个。整个框架基于该类型的回调。没有lambda的常见用法示例:

typedef void (Object::*Callback)();
class MyObject : public Object
{
    void mystuff()
    {
        do_some_work(this, (Callback)(MyClass::do_work));
    }
    void do_work()
    {
        // here the work is done
    }
};

解决方案以下是基于Marcelo答案的解决方案:

class CallbackWrapper : public Object
{
    fptr fptr_;
public:
    CallbackWrapper(void (*fptr)()) : fptr_(fptr) { }
    void execute()
    {
        *fptr_();
    }
};

class MyObject : public Object
{
    void mystuff()
    {
        CallbackWrapper* do_work = new CallbackWrapper([]()
        {
           /* this lambda is passed */
        });
        do_some_work(do_work, (Callback)(CallbackWrapper::execute));
    }
};

由于我们创建了CallbackWrapper,因此我们可以控制它的生命周期,以用于异步使用回调的情况。谢谢大家。

4 个答案:

答案 0 :(得分:6)

这是不可能的。构造(optr->*fptr)()要求fptr是指向成员的指针。如果do_some_work在您的控制之下,请将其更改为采用与lambda函数兼容的内容,例如std::function<void()>或参数化类型。如果它是一个不受你控制的遗留框架,你可以将它包装起来,如果它是一个功能模板,例如:

template <typename Object>
do_some_work(Object* optr, void (Object::*fptr)());

然后,您可以实现包装器模板:

template <typename F>
void do_some_work(F f) {
    struct S {
        F f;
        S(F f) : f(f) { }
        void call() { f(); delete this; }
    };
    S* lamf = new S(f);
    do_some_work(lamf, &S::call);
}

class MyObject // You probably don't need this class anymore.
{
    void mystuff()
    {
        do_some_work([](){ /* Do your thing... */ });
    }
};

编辑:如果do_some_work异步完成,则必须在堆上分配lamf。我相应地修改了上面的代码,只是为了安全起见。感谢@David Rodriguez指出这一点。

答案 1 :(得分:1)

与语法不匹配相比,您尝试采用的方法存在更深层次的问题。正如DeadMG建议的那样,最好的解决方案是改进do_some_work的接口以采用某种类型的仿函数(std::function<void()>在C ++ 11中或使用boost,甚至是通用的F on operator()被称为。{/ p>

Marcelo提供的解决方案解决了语法不匹配问题,但由于库通过指针获取第一个元素,因此调用者有责任确保在执行回调时对象将处于活动状态。假设回调是异步的,他的解决方案(以及其他类似的替代方案)的问题是在执行回调之前可能会破坏对象,从而导致未定义的行为。

我建议你使用某种形式的 plimp 习语,这种情况下的目标是隐藏回调的需要(因为其余的实现可能不需要隐藏你可以使用另一个类来处理回调但是按值存储它,如果你不想动态分配更多的内存):

class MyClass;
class MyClassCallbacks {
   MyClass* ptr;
public:
   MyClassCallbacks( MyClass* ptr ) : ptr(ptr) {}
// callbacks that execute code on `ptr`
   void callback1() {
      // do some operations
      // update *ptr
   }
};
class MyClass {
   MyClassCallbacks callbackHandler;
public:
   void mystuff() {
      do_some_work( &callbackHandler, &MyClassHandler::callback1 );
   }
};

在这个设计中,这两个类是分开的,但代表一个唯一的单个实体,所以可以添加一个朋友声明,让MyClassCallbacks访问MyClass中的内部数据(两者都是一个单个实体,除了提供更清晰的接口,但耦合已经很高,因此添加friend所需的额外耦合是没有问题的。

因为MyClassMyClassCallbacks个实例之间存在1-1关系,所以它们的生命周期受到约束,除了在销毁期间不存在生命周期问题。在销毁期间,您必须确保在MyClass对象被销毁时没有注册可以启动的回调。

既然你在这里,你可能想要走得更远,并做一个正确的 pimpl :将所有数据和实现移动到指针所持有的不同类型,并提供一个MyClass存储指针并仅提供公共函数,实现为 pimpl 对象的转发器。当你使用继承时,这可能有些棘手,并且 pimpl 习惯用法在类型层次结构上实现有点麻烦(如果你需要扩展MyClass,那么派生自Object可以在 pimpl 对象中完成,而不是在接口类型中完成。

答案 2 :(得分:0)

我认为你不能那样做。您的do_some_work()被声明为接受指向类Object方法的指针,因此应该提供这样的指针。否则optr->*fptr无效,因为lambda不是Object的成员。您可能应该尝试使用std::function并在其关闭中添加所需的Object成员。

答案 3 :(得分:-1)

您必须使用std::function<void()>。函数和成员函数指针都非常不适合作为回调。