Co to jest delegat C ++?

147

Jaka jest ogólna idea delegata w C ++? Czym one są, jak są używane i do czego służą?

Chciałbym najpierw dowiedzieć się o nich w sposób „czarnej skrzynki”, ale trochę informacji o wnętrznościach tych rzeczy też byłoby świetne.

To nie jest C ++ w najczystszej lub najczystszej postaci, ale zauważam, że baza kodu, w której pracuję, ma ich pod dostatkiem. Mam nadzieję, że wystarczająco je zrozumiem, więc mogę ich po prostu użyć i nie będę musiał zagłębiać się w okropną okropność zagnieżdżonego szablonu.

Te dwa artykuły w The Code Project wyjaśniają, co mam na myśli, ale niezbyt zwięźle:

SirYakalot
źródło
4
Czy mówisz o zarządzanym C ++ pod .NET?
dasblinkenlight
2
Czy spojrzałeś na stronę Wikipedii dotyczącą delegacji ?
Matthijs Bierman
5
delegatenie jest popularną nazwą w języku c ++. Powinieneś dodać trochę informacji do pytania, aby uwzględnić kontekst, w którym je przeczytałeś. Zwróć uwagę, że chociaż wzorzec może być wspólny, odpowiedzi mogą się różnić, jeśli mówisz o delegacie w ogóle lub w kontekście C ++ CLI lub dowolnej innej biblioteki, która ma określoną implementację delegata .
David Rodríguez - dribeas
6
czekać 2 lata
?;
6
Wciąż czekam ...
Grimm The Opiner

Odpowiedzi:

173

Masz niesamowitą liczbę możliwości osiągnięcia delegatów w C ++. Oto te, które przyszły mi do głowy.


Opcja 1: funktory:

Obiekt funkcji można utworzyć poprzez implementację 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);

Opcja 2: wyrażenia 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);

Opcja 3: wskaźniki funkcji

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

Opcja 4: wskaźnik do funkcji składowych (najszybsze rozwiązanie)

Zobacz Szybki delegat C ++ (w projekcie kodu ).

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

Opcja 5: std :: function

(lub boost::functionjeśli Twoja standardowa biblioteka go nie obsługuje). Jest wolniejszy, ale jest najbardziej elastyczny.

#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

Opcja 6: Wiązanie (przy użyciu std :: bind )

Pozwala na ustawienie niektórych parametrów z wyprzedzeniem, na przykład wygodne wywołanie funkcji składowej.

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

Opcja 7: szablony

Akceptuj wszystko, o ile pasuje do listy argumentów.

template <class FunctionT>
int DoSomething(FunctionT func)
{
    return func(3.14);
}
JN
źródło
2
Świetna lista, +1. Jednak tylko dwa tak naprawdę liczą się jako delegaci - przechwytywanie lambd i zwracanego obiektu std::bind, i oba naprawdę robią to samo, z wyjątkiem tego, że lambdy nie są polimorficzne w tym sensie, że mogą akceptować różne typy argumentów.
Xeo
1
@JN: Dlaczego nie zaleca się używania wskaźników do funkcji, ale wydaje się, że nie przeszkadza to używanie wskaźników do metod składowych? Są po prostu identyczne!
Matthieu M.
1
@MatthieuM. : uczciwa uwaga. Rozważałem wskaźniki funkcji jako dziedzictwo, ale to chyba tylko mój osobisty gust.
JN
1
@Xeo: mój pomysł na delegata jest raczej empiryczny. Może mieszam zbyt dużo obiektu funkcji i delegata (obwiniam moje poprzednie doświadczenie C #).
JN
2
@SirYakalot Coś, co zachowuje się jak funkcja, ale może jednocześnie utrzymywać stan i można nim manipulować jak każdą inną zmienną. Jednym z zastosowań jest na przykład przyjęcie funkcji, która przyjmuje dwa parametry i wymusza na pierwszym parametrze określoną wartość, tworząc nową funkcję z jednym parametrem ( bindingna mojej liście). Nie można tego osiągnąć za pomocą wskaźników funkcji.
JN
37

Delegat to klasa, która otacza wskaźnik lub odwołanie do instancji obiektu, metodę składową klasy tego obiektu, która ma zostać wywołana w tym wystąpieniu obiektu, i udostępnia metodę wyzwalającą to wywołanie.

Oto przykład:

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 );
}
Grimm The Opiner
źródło
który zapewnia metodę wyzwalania połączenia? delegat? w jaki sposób? wskaźnik funkcji?
SirYakalot
Klasa delegata będzie oferować metodę podobną do Execute()wyzwalania wywołania funkcji na obiekcie zawijanym przez delegata.
Grimm The Opiner
4
Zamiast wykonywania, zdecydowanie zalecałbym w twoim przypadku zastąpienie operatora wywołania - void CCallback :: operator () (int). Powodem tego jest programowanie ogólne, oczekuje się, że wywoływalne obiekty będą wywoływane jak funkcja. o.Execute (5) byłoby niekompatybilne, ale o (5) dobrze by pasowało jako wywoływalny argument szablonu. Tę klasę można również uogólnić, ale zakładam, że utrzymałeś ją w prostocie dla zwięzłości (co jest dobrą rzeczą). Poza tym czubkiem dzioba jest to bardzo przydatna klasa.
David Peterson,
18

Potrzeba implementacji delegatów C ++ jest długotrwałym wstydem dla społeczności C ++. Każdy programista C ++ chciałby je mieć, więc ostatecznie używają ich pomimo faktów, że:

  1. std::function() używa operacji na stercie (i jest poza zasięgiem poważnego programowania osadzonego).

  2. Wszystkie inne implementacje idą na ustępstwa w kierunku przenośności lub zgodności ze standardami w większym lub mniejszym stopniu (prosimy o sprawdzenie, sprawdzając różne implementacje delegatów tutaj i na projekcie kodu). Nie widziałem jeszcze implementacji, która nie używa dzikich reinterpret_casts, "prototypów" zagnieżdżonych klas, które, mam nadzieję, wytwarzają wskaźniki funkcji o takim samym rozmiarze, jak ten przekazany przez użytkownika, sztuczki kompilatora, takie jak najpierw deklaracja w przód, a następnie typedef, a następnie deklaracja ponownie, tym razem dziedziczenie z innej klasy lub podobnych podejrzanych technik. Chociaż jest to wielkie osiągnięcie dla osób wdrażających, które to stworzyły, wciąż jest smutnym świadectwem ewolucji C ++.

  3. Rzadko kiedy wskazuje się, że obecnie ponad 3 standardowe wersje C ++ delegatów nie zostały odpowiednio zaadresowane. (Lub brak funkcji językowych, które pozwalają na proste implementacje delegatów).

  4. Ze sposobem, w jaki funkcje lambda C ++ 11 są definiowane przez standard (każda lambda ma anonimowy, inny typ), sytuacja poprawiła się tylko w niektórych przypadkach użycia. Jednak w przypadku użycia delegatów w interfejsach API bibliotek (DLL) same lambdy nadal nie są użyteczne. Typową techniką jest tutaj najpierw spakowanie lambdy do funkcji std ::, a następnie przekazanie jej przez interfejs API.

BitTickler
źródło
Zrobiłem wersję ogólnych wywołań zwrotnych Elberta Mai w C ++ 11 w oparciu o inne pomysły widziane gdzie indziej i wydaje się, że rozwiązuje ona większość problemów przytoczonych w 2). Jedyny pozostały mi dokuczliwy problem polega na tym, że utknąłem przy użyciu makra w kodzie klienta do tworzenia delegatów. Prawdopodobnie istnieje magiczne wyjście z tego szablonu, którego jeszcze nie rozwiązałem.
kert
3
Używam funkcji std :: bez sterty w systemie wbudowanym i nadal działa.
prasad
1
std::functionnie zawsze dynamicznie alokuje
Lightness Races in Orbit
3
Ta odpowiedź jest bardziej rantem niż rzeczywistą odpowiedzią
Lightness Races in Orbit
8

Mówiąc najprościej, delegat zapewnia funkcjonalność opisującą, jak powinien działać wskaźnik funkcji. Istnieje wiele ograniczeń wskaźników funkcji w C ++. Delegat używa zakulisowych szablonów, aby utworzyć obiekt typu wskaźnika funkcji klasy szablonu, który działa tak, jak chcesz.

tj. - możesz ustawić je tak, aby wskazywały na daną funkcję i możesz je przekazywać i wywoływać kiedykolwiek i gdziekolwiek chcesz.

Oto kilka bardzo dobrych przykładów:

SirYakalot
źródło
4

Opcją dla delegatów w C ++, która nie została tutaj inaczej wymieniona, jest zrobienie tego w stylu C przy użyciu funkcji ptr i argumentu kontekstu. Jest to prawdopodobnie ten sam wzorzec, którego wielu ludzi zadających to pytanie stara się uniknąć. Ale wzorzec jest przenośny, wydajny i można go używać w kodzie osadzonym i jądrze.

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);
    ...
Joe
źródło
1

Odpowiednik środowiska wykonawczego systemu Windows obiektu funkcji w standardowym C ++. Można użyć całej funkcji jako parametru (właściwie jest to wskaźnik funkcji). Jest używany głównie w połączeniu z wydarzeniami. Delegat reprezentuje kontrakt, który w znacznym stopniu spełniają programy obsługi zdarzeń. Ułatwia to, jak może działać wskaźnik funkcji.

nerw
źródło