什么是C ++委托?


147

C ++中委托的一般想法是什么?它们是什么?它们的用途是什么?

我想首先以“黑匣子”的方式来了解它们,但是对这些东西的勇气有一点了解也是非常有用的。

这并不是最纯净或最纯净的C ++,但我注意到我工作的代码库中有大量的C ++。我希望对它们有足够的了解,因此我可以使用它们,而不必深入研究嵌套模板的可怕问题。

这两篇代码项目文章解释了我的意思,但并没有特别简洁:


4
您是在谈论.NET下的托管C ++吗?
dasblinkenlight 2012年


5
delegate在c ++术语中不是通用名称。您应该在问题中添加一些信息,以包括您在其中阅读过的上下文。请注意,虽然这种模式可能很常见,但是如果您一般地谈论委托,或者在C ++ CLI或具有委托特定实现的任何其他库的上下文中,答案可能会有所不同。
DavidRodríguez-dribeas 2012年

6
等待2年?;)
1

6
仍在等待中……
格林(Grimm)The Opiner

Answers:


173

您可以通过多种选择来实现C ++中的委托。这是我想到的那些。


选项1:函子:

可以通过实现来创建功能对象 operator()

struct Functor
{
     // Normal class/struct members

     int operator()(double d) // Arbitrary return types and parameter list
     {
          return (int) d + 1;
     }
};

// Use:
Functor f;
int i = f(3.14);

选项2:lambda表达式(仅C ++ 11

// Syntax is roughly: [capture](parameter list) -> return type {block}
// Some shortcuts exist
auto func = [](int i) -> double { return 2*i/1.15; };
double d = func(1);

选项3:函数指针

int f(double d) { ... }
typedef int (*MyFuncT) (double d);
MyFuncT fp = &f;
int a = fp(3.14);

选项4:指向成员函数的指针(最快的解决方案)

请参见Fast C ++ Delegate(在The Code Project上)。

struct DelegateList
{
     int f1(double d) { }
     int f2(double d) { }
};

typedef int (DelegateList::* DelegateType)(double d);

DelegateType d = &DelegateList::f1;
DelegateList list;
int a = (list.*d)(3.14);

选项5:std :: function

(或者boost::function如果您的标准库不支持它)。它比较慢,但是最灵活。

#include <functional>
std::function<int(double)> f = [can be set to about anything in this answer]
// Usually more useful as a parameter to another functions

选项6:绑定(使用std :: bind

允许预先设置一些参数,例如方便调用成员函数。

struct MyClass
{
    int DoStuff(double d); // actually a DoStuff(MyClass* this, double d)
};

std::function<int(double d)> f = std::bind(&MyClass::DoStuff, this, std::placeholders::_1);
// auto f = std::bind(...); in C++11

选项7:模板

接受与参数列表匹配的任何内容。

template <class FunctionT>
int DoSomething(FunctionT func)
{
    return func(3.14);
}

2
很棒的清单,+ 1。但是,这里只有两个真正算作委托-捕获lambda和从返回的对象std::bind,它们的确做相同的事情,只是lambda并不是多态的,因为它们可以接受不同的参数类型。
Xeo 2012年

1
@JN:为什么建议不要使用函数指针,但使用成员方法指针似乎可以呢?他们是一样的!
Matthieu M.

1
@MatthieuM。: 有道理。我当时认为函数指针是旧的,但这可能仅仅是我个人的喜好。
2012年

1
@Xeo:我对代表的想法比较有经验。也许我是mixig太多的函数对象和委托(怪我以前的C#经验)。
2012年

2
@SirYakalot行为类似于函数,但可以同时保持状态,并且可以像其他任何变量一样进行操作。例如,一种用途是采用一个带有两个参数的函数,并强制第一个参数具有确定的值,从而使用单个参数(binding在我的列表中)创建一个新函数。使用函数指针无法实现此目的。
2012年

37

委托是一个类,它包装指向对象实例的指针或引用,该对象实例的成员方法将在该对象实例上调用,并提供触发该调用的方法。

这是一个例子:

template <class T>
class CCallback
{
public:
    typedef void (T::*fn)( int anArg );

    CCallback(T& trg, fn op)
        : m_rTarget(trg)
        , m_Operation(op)
    {
    }

    void Execute( int in )
    {
        (m_rTarget.*m_Operation)( in );
    }

private:

    CCallback();
    CCallback( const CCallback& );

    T& m_rTarget;
    fn m_Operation;

};

class A
{
public:
    virtual void Fn( int i )
    {
    }
};


int main( int /*argc*/, char * /*argv*/ )
{
    A a;
    CCallback<A> cbk( a, &A::Fn );
    cbk.Execute( 3 );
}

哪个提供了触发呼叫的方法?代表?怎么样?功能指针?
SirYakalot'3

委托类将提供类似的方法Execute(),该方法将触发委托包装的对象上的函数调用。
格林(Grimm The Opiner)2012年

4
在您的情况下,我强烈建议您改写调用运算符,而不是Execute-void CCallback :: operator()(int)。这样做的原因是在通用编程中,可调用对象应像函数一样被调用。o.Execute(5)是不兼容的,但是o(5)非常适合作为可调用模板参数。此类也可以泛化,但为简洁起见,我假设您将其简化了(这是一件好事)。除了该nitpick,这是一个非常有用的类。
David Peterson 2014年

18

对C ++委托实现的需求是对C ++社区的长期困扰。每个C ++程序员都希望拥有它们,因此尽管存在以下事实,他们还是最终使用了它们:

  1. std::function() 使用堆操作(对于严肃的嵌入式编程是无法实现的)。

  2. 所有其他实现都对可移植性或标准一致性做出了或多或少的让步(请通过检查此处和代码项目中的各种委托实现进行验证)。我还没有看到一个不使用野生的reinterpret_casts的实现,嵌套类“原型”希望产生与用户传递的指针大小相同的函数指针,编译器的技巧如第一个前向声明,然后是typedef然后再声明,这次是从另一个班级或类似的黑幕技术继承下来的。虽然这对于实现此目标的实现者来说是一项巨大的成就,但对于C ++的发展仍然是一个可悲的见证。

  3. 很少有人指出,现在超过3个C ++标准修订版,没有正确处理委托。(或者缺少允许直接实现委托实现的语言功能。)

  4. 通过标准定义C ++ 11 lambda函数的方式(每个lambda具有匿名的不同类型),这种情况仅在某些用例中得到了改善。但是对于在(DLL)库API中使用委托的用例, lambda 仍然不可用。此处的常用技术是先将lambda打包到std :: function中,然后将其通过API传递。


我已经根据其他地方看到的其他思想在C ++ 11中完成了Elbert Mai的通用回调的版本,它似乎确实清除了您在2)中引用的大多数问题。对我而言,唯一剩下的困扰问题是我在客户端代码中使用宏来创建委托。我可能还没有解决模板魔术的方法。
kert

3
我在嵌入式系统中使用std :: function而没有堆,它仍然可以工作。
prasad

1
std::function并不总是动态分配的
Lightness Race in Orbit

3
这个答案比实际答案更具说服力
Lightness Races in Orbit

8

很简单,委托提供了函数指针应如何工作的功能。C ++中的函数指针有很多限制。委托使用一些幕后的模板样式来创建模板类的功能指针类型的事物,该事物以您可能希望的方式工作。

即-您可以将它们设置为指向给定的函数,并且可以将它们传递给您,并随时随地调用它们。

这里有一些很好的例子:


4

对于C ++中的委托,没有在这里另外提及的一个选项是使用函数ptr和上下文参数来实现C样式。这可能与许多提出此问题的人试图避免的模式相同。但是,该模式是可移植的,高效的,并且可以在嵌入式和内核代码中使用。

class SomeClass
{
    in someMember;
    int SomeFunc( int);

    static void EventFunc( void* this__, int a, int b, int c)
    {
        SomeClass* this_ = static_cast< SomeClass*>( this__);

        this_->SomeFunc( a );
        this_->someMember = b + c;
    }
};

void ScheduleEvent( void (*delegateFunc)( void*, int, int, int), void* delegateContext);

    ...
    SomeClass* someObject = new SomeObject();
    ...
    ScheduleEvent( SomeClass::EventFunc, someObject);
    ...

1

Windows运行时等效于标准C ++中的功能对象。可以将整个函数用作参数(实际上是一个函数指针)。它通常与事件结合使用。委托代表事件处理程序可以充分履行的合同。它有助于函数指针的工作方式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.