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:


4
Czy mówisz o zarządzanym C ++ pod .NET?
— dasblinkenlight


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

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

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.


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:


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

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.