Что такое делегат C ++?

147

Какова общая идея делегата в C ++? Что они, как они используются и для чего они используются?

Я хотел бы сначала узнать о них «черным ящиком», но немного информации о внутренностях этих вещей тоже было бы здорово.

Это не C ++ в его самом чистом или чистом виде, но я замечаю, что кодовая база, где я работаю, имеет их в изобилии. Я надеюсь понять их достаточно, так что я могу просто использовать их и не нужно углубляться в ужасную ужасность вложенных шаблонов.

Эти две статьи The Code Project объясняют, что я имею в виду, но не очень кратко:

SirYakalot
источник
4
Вы говорите об управляемом C ++ в .NET?
dasblinkenlight
2
Вы смотрели на страницу Википедии для Делегации ?
Маттис Бирман
5
delegateне является распространенным именем на языке С ++. Вы должны добавить некоторую информацию к вопросу, чтобы включить контекст, в котором вы ее прочитали. Обратите внимание, что хотя шаблон может быть общим, ответы могут отличаться, если вы говорите о делегате в целом или в контексте C ++ CLI или любой другой библиотеки, в которой есть конкретная реализация делегата .
Дэвид Родригес - dribeas
6
ждать 2 года?;)
ранг1
6
Все еще жду ...
Гримм Опинер

Ответы:

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: лямбда-выражения (только 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проекте кода ).

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);
}
JN
источник
2
Отличный список, +1. Однако только два действительно считаются делегатами - захват лямбд и возвращаемый объект std::bind, и оба действительно делают одно и то же, за исключением того, что лямбды не полиморфны в том смысле, что они могут принимать разные типы аргументов.
Xeo
1
@JN: Почему вы рекомендуете не использовать указатели на функции, но, похоже, все в порядке с использованием указателей на методы-члены? Они просто идентичны!
Матье М.
1
@MatthieuM. : Честная оценка. Я считал функциональные указатели устаревшими, но это, вероятно, только мой личный вкус.
JN
1
@Xeo: моя идея делегата довольно эмпирическая. Может быть, я смешиваю слишком много функциональных объектов и делегатов (виноват мой предыдущий опыт работы с C #).
JN
2
@SirYakalot Что-то, что ведет себя как функция, но может одновременно содержать состояние и может управляться как любая другая переменная. Например, можно использовать функцию, которая принимает два параметра и заставить первый параметр иметь определенное значение, создавая новую функцию с одним параметром ( bindingв моем списке). Вы не можете достичь этого с помощью функциональных указателей.
JN
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
Класс делегата предложит метод, подобный тому, Execute()который инициирует вызов функции на объекте, который переносит делегат.
Гримм Опинер
4
Вместо выполнения я бы настоятельно рекомендовал в вашем случае переопределить оператор вызова - void CCallback :: operator () (int). Причина этого в общем программировании: вызываемые объекты должны вызываться как функции. o.Execute (5) был бы несовместим, но o (5) прекрасно подходил бы в качестве аргумента вызываемого шаблона. Этот класс также может быть обобщен, но я предполагаю, что вы сохранили его для краткости (что хорошо). Кроме этого придирки, это очень полезный класс.
Дэвид Петерсон
18

Потребность в реализациях делегатов C ++ является длительным затруднением для сообщества C ++. Каждый программист C ++ хотел бы иметь их, поэтому они в конечном итоге используют их, несмотря на то, что:

  1. std::function() использует операции кучи (и недоступен для серьезного встроенного программирования).

  2. Все другие реализации идут на уступки либо к переносимости, либо к стандартному соответствию в большей или меньшей степени (пожалуйста, проверьте, осмотрев различные реализации делегатов здесь и в проекте кода). Мне еще предстоит увидеть реализацию, которая не использует дикие reinterpret_casts, «прототипы» вложенного класса, которые, как мы надеемся, выдают указатели на функции того же размера, что и передаваемый пользователем, трюки компилятора, такие как сначала forward forward, затем затем typedef, затем снова объявляют, на этот раз наследование от другого класса или аналогичных теневых методов. Несмотря на то, что это большое достижение для разработчиков, которые его создали, это все еще печальное свидетельство того, как развивается С ++.

  3. Лишь в редких случаях указывается, что в настоящее время более 3 стандартных версий C ++ делегаты не были должным образом рассмотрены. (Или нехватка языковых функций, которые допускают прямую реализацию делегатов.)

  4. Благодаря тому, как лямбда-функции C ++ 11 определяются стандартом (у каждой лямбда-функции есть анонимный, различный тип), ситуация только улучшилась в некоторых случаях использования. Но для случая использования делегатов в (DLL) библиотечных API, одни только лямбды не могут быть использованы. Обычный способ - сначала упаковать лямбда-выражение в функцию std ::, а затем передать ее через API.

BitTickler
источник
Я сделал версию универсальных обратных вызовов Элберта Мая в C ++ 11, основанную на других идеях, замеченных в других местах, и она, кажется, проясняет большинство проблем, которые вы цитируете в 2). Единственная остающаяся проблема для меня - я застрял, используя макрос в клиентском коде для создания делегатов. Вероятно, есть волшебный выход из этого шаблона, который я еще не решил.
Kert
3
Я использую std :: function без кучи во встроенной системе, и она все еще работает.
прасад
1
std::functionне всегда динамически распределяется
Гонки
3
Этот ответ скорее разглагольствует, чем фактический ответ
Гонки
8

Очень просто, делегат обеспечивает функциональность того, как ДОЛЖЕН работать указатель на функцию. Есть много ограничений указателей функций в C ++. Делегат использует некоторую скрытность шаблона для создания функции-указателя-типа-класса-класса, которая работает так, как вы этого хотите.

то есть - вы можете установить их так, чтобы они указывали на данную функцию, и вы можете передавать их и вызывать их в любое время и в любом месте.

Здесь есть несколько очень хороших примеров:

SirYakalot
источник
4

Опция для делегатов в C ++, которая здесь не упоминается, заключается в том, чтобы сделать это в стиле C, используя функцию ptr и аргумент контекста. Вероятно, это та же самая схема, которую многие, задавая этот вопрос, пытаются избежать. Но шаблон является переносимым, эффективным и может использоваться во встроенном коде и коде ядра.

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 Runtime эквивалент функционального объекта в стандарте C ++. Можно использовать всю функцию в качестве параметра (фактически это указатель на функцию). В основном используется в сочетании с событиями. Делегат представляет собой контракт, который обработчики событий очень выполняют. Это облегчает работу указателя функции.

nmserve
источник