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


147

Яке загальне уявлення про делегата в C ++? Що вони, як їх використовують і для чого вони використовуються?

Спочатку я хотів би дізнатися про них «чорною скринькою», але трохи інформації про кишки цих речей теж було б чудово.

Це не C ++ в його найчистішому чи найчистішому, але я помічаю, що база даних коду, де я працюю, має їх у достатку. Я сподіваюся їх достатньо зрозуміти, тому можу просто використати їх і не доведеться заглиблюватися в жахливу гніздову вкладеність шаблону.

Ці дві статті проекту «Кодекс» пояснюють, що я маю на увазі, але не особливо лаконічно:


4
Ви говорите про керований C ++ під .NET?
dasblinkenlight


5
delegateне є загальною назвою в мові c ++. Ви повинні додати інформацію до питання, щоб включити контекст, у якому ви його прочитали. Зауважте, що хоча шаблон може бути загальним, відповіді можуть відрізнятися, якщо ви говорите про делегата в цілому або в контексті C ++ CLI або будь-якої іншої бібліотеки, яка має певну реалізацію делегата .
Девід Родрігес - дрибес

6
зачекайте 2 роки
?;

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: вказівник на функції члена (найшвидше рішення)

Див. Швидкий делегат C ++проекті The Code ).

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 :: функція

(або 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. Однак тут делегати вважаються лише двома - захоплення лямбда і об'єкт, повернутий звідти std::bind, і обидва дійсно роблять те саме, за винятком того, що лямбди не є поліморфними в тому сенсі, що вони можуть приймати різні типи аргументів.
Xeo

1
@JN: Чому ви не рекомендуєте використовувати вказівники функції, але, здається, не в порядку з використанням покажчиків методів-членів? Вони просто однакові!
Маттьє М.

1
@MatthieuM. : справедлива точка. Я розглядав функціональні покажчики як спадщину, але це, мабуть, лише мій особистий смак.
JN

1
@Xeo: моя ідея делегата досить емпірична. Можливо, я міксиг занадто багато об'єкта функції та делегата (звинувачую мій колишній досвід C #).
JN

2
@SirYakalot Щось, що поводиться як функція, але може одночасно мати стан і може маніпулювати, як будь-яка інша змінна. Наприклад, одне використання - це взяти функцію, яка бере два параметри і змусити 1-й параметр мати певне значення, створивши нову функцію з одним параметром ( 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 );
}

який забезпечує метод для виклику? делегат? як? функція вказівника?
СерЯкалот

Клас делегації запропонує такий метод, Execute()який викликає виклик функції на об'єкті, який делегат завершує.
Грімм Оглядач

4
Замість виконання, я настійно рекомендую у вашому випадку переосмислити оператора виклику - недійсний CCallback :: operator () (int). Причина цього полягає в загальному програмуванні, очікувані об'єкти, що викликаються, повинні називатися функцією. o.Eeecute (5) буде несумісним, але o (5) добре підходить як аргумент шаблону, що викликається. Цей клас також можна було б узагальнити, але я припускаю, що ви простий для стислості (що добре). Окрім цієї нітпіки, це дуже корисний клас.
Девід Петерсон

18

Необхідність реалізації делегатів C ++ є довготривалим захопленням спільноти C ++. Кожен програміст на C ++ хотів би їх мати, тому він врешті-решт використовує їх, незважаючи на факти, які:

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

  2. Всі інші реалізації реалізують поступки або на портативність, або на стандартну відповідність у більшій чи меншій мірі (будь ласка, перевірте це, ознайомившись із різними делегатськими реалізаціями тут та у кодовому проекті). Мені ще не доводиться бачити реалізацію, яка не використовує диких reinterpret_casts, "прототипи" вкладеного класу, які, сподіваємось, створюють покажчики функцій такого ж розміру, як і переданий користувачем, трюки компілятора, як спочатку оголосити вперед, потім typedef, а потім знову оголосити, цього разу успадковуючи від іншого класу або подібних тінистих прийомів. Хоча це великий успіх для тих, хто це створив, це все ще сумний відгук про те, як розвивається C ++.

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

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


Я зробив версію загальних зворотних викликів Ельберта Май в C ++ 11, засновану на інших ідеях, що бачаться в інших місцях, і, здається, очищає більшість проблем, які ви цитуєте в 2). Єдиною проблемою, що залишається для мене, я застряг, використовуючи макрос у коді клієнта для створення делегатів. Мабуть, магічний вихід з цього шаблону, який я ще не вирішив.
kert

3
Я використовую std :: function без купи у вбудованій системі, і вона все ще працює.
прасад

1
std::functionне завжди динамічно виділяють
Гонки

3
Ця відповідь є скоріше грішною, ніж реальною відповіддю
гонки легкості в орбіті

8

Дуже просто, делегат надає функціонал для того, як функція Вказівника ДОБАЄ працювати. У C ++ існує багато обмежень функціональних покажчиків. Делегат використовує деяку неприйнятність шаблону шаблону, щоб створити функцію шаблону класу-функцію-вказівника-типу, яка працює так, як ви цього хочете.

тобто - ви можете встановити їх, щоб вони вказували на певну функцію, і ви можете передавати їх і називати їх коли і де завгодно.

Тут є кілька дуже хороших прикладів:


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 ++. Можна використовувати всю функцію як параметр (насправді це покажчик функції). В основному використовується в поєднанні з подіями. Делегат представляє договір, який обробники подій значно виконують. Це полегшує функціонування покажчика функції.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.