6

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

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

解决方案这是我基于马塞洛回答的解决方案:

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

4 回答 4

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 指出这一点。

于 2012-08-10T12:14:39.743 回答
1

您尝试采用的方法存在比语法不匹配更深层次的问题。正如 DeadMG 建议的那样,最好的解决方案是改进接口do_some_work以采用某种函子(在 C++11 中或使用 boost ,std::function<void()>甚至是调用的泛型。Foperator()

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是没有问题的)。

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

既然你在它上面,你可能想要加倍努力并做一个适当的pimpl:将所有数据和实现移动到由指针保存的不同类型中,并提供一个MyClass存储指针并仅提供公共功能的, 实现为pimpl对象的转发器。当您使用继承时,这可能会有些棘手,并且pimpl习惯用法在类型层次结构上实现有点麻烦(如果您需要扩展,则可以在pimplMyClass对象中进行派生,而不是在接口类型中完成)。Object

于 2012-08-10T12:52:06.467 回答
0

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

于 2012-08-10T12:06:13.580 回答
-1

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

于 2012-08-10T12:04:25.187 回答