Was ist ein C++-Delegat?

Lesezeit: 8 Minuten

Benutzer-Avatar
Dollarscheibe

Was ist die allgemeine Idee eines Delegaten in C++? Was sind sie, wie werden sie verwendet und wofür werden sie verwendet?

Ich würde sie gerne zuerst auf „Black Box“-Weise kennenlernen, aber ein paar Informationen über die Eingeweide dieser Dinge wären auch großartig.

Dies ist nicht C++ in seiner reinsten oder saubersten Form, aber ich stelle fest, dass die Codebasis, an der ich arbeite, sie in Hülle und Fülle enthält. Ich hoffe, sie genug zu verstehen, damit ich sie einfach verwenden kann und mich nicht in die schreckliche Schrecklichkeit verschachtelter Vorlagen vertiefen muss.

Diese zwei Das Code-Projekt Artikel erklären, was ich meine, aber nicht besonders prägnant:

  • Reden Sie von Managed C++ unter .NET?

    – Sergej Kalinitschenko

    5. März 2012 um 14:20 Uhr

  • Hast du dir die angeschaut Wikipedia-Seite für Delegation?

    – Matthijs Biermann

    5. März 2012 um 14:20 Uhr

  • delegate ist kein gebräuchlicher Name im C++-Sprachgebrauch. Sie sollten der Frage einige Informationen hinzufügen, um den Kontext einzubeziehen, in dem Sie sie gelesen haben. Beachten Sie, dass das Muster zwar üblich sein kann, die Antworten jedoch unterschiedlich sein können, wenn Sie darüber sprechen delegieren im Allgemeinen oder im Kontext von C++CLI oder einer anderen Bibliothek, die eine bestimmte Implementierung von hat delegieren.

    – David Rodríguez – Dribeas

    5. März 2012 um 15:00 Uhr

  • 2 jahre warten ?;)

    – Rang 1

    18. Juni 2013 um 14:39 Uhr

  • Warte noch…

    – Grimm der Opiner

    25. Juni 2014 um 7:56 Uhr

Benutzer-Avatar
JN

Sie haben eine unglaubliche Anzahl von Möglichkeiten, um Delegates in C++ zu erreichen. Hier sind die, die mir in den Sinn gekommen sind.


Option 1: Funktoren:

Ein Funktionsobjekt kann durch Implementieren erstellt werden 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);

Option 2: Lambda-Ausdrücke (C++11 nur)

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

Option 3: Funktionszeiger

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

Option 4: Zeiger auf Elementfunktionen (schnellste Lösung)

Sehen Schneller C++-Delegierter (An Das Code-Projekt).

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

Möglichkeit 5: std::Funktion

(oder boost::function wenn Ihre Standardbibliothek dies nicht unterstützt). Es ist langsamer, aber am flexibelsten.

#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

Option 6: Bindung (mit std::bind)

Ermöglicht das Einstellen einiger Parameter im Voraus, z. B. zum bequemen Aufrufen einer Member-Funktion.

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

Option 7: Vorlagen

Akzeptiere alles, solange es mit der Argumentliste übereinstimmt.

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

  • Tolle Liste, +1. Allerdings zählen hier nur zwei wirklich als Delegaten – das Erfassen von Lambdas und das zurückgegebene Objekt std::bindund beide tun wirklich dasselbe, außer dass Lambdas nicht in dem Sinne polymorph sind, dass sie unterschiedliche Argumenttypen akzeptieren können.

    – Xeo

    5. März 2012 um 14:46 Uhr


  • @JN: Warum empfehlen Sie, keine Funktionszeiger zu verwenden, scheinen aber mit der Verwendung der Member-Methodenzeiger einverstanden zu sein? Sie sind einfach identisch!

    – Matthias M.

    5. März 2012 um 14:46 Uhr

  • @MatthieuM. : gutes Argument. Ich hielt Funktionszeiger für veraltet, aber das ist wahrscheinlich nur mein persönlicher Geschmack.

    – JN

    5. März 2012 um 14:51 Uhr

  • @SirYakalot Etwas, das sich wie eine Funktion verhält, aber gleichzeitig den Zustand halten und wie jede andere Variable manipuliert werden kann. Eine Verwendung besteht beispielsweise darin, eine Funktion zu nehmen, die zwei Parameter benötigt, und den 1. Parameter zu zwingen, einen bestimmten Wert zu haben, um eine neue Funktion mit einem einzigen Parameter zu erstellen (binding in meiner Liste). Sie können dies nicht mit Funktionszeigern erreichen.

    – JN

    6. März 2012 um 15:05 Uhr


  • Sie existieren in C++ per se nicht. Daher die Liste.

    – JN

    25. Juli 2016 um 3:29 Uhr

Benutzer-Avatar
Grimm Der Opiner

Ein Delegat ist eine Klasse, die einen Zeiger oder eine Referenz auf eine Objektinstanz umschließt, eine Mitgliedsmethode der Klasse dieses Objekts, die für diese Objektinstanz aufgerufen werden soll, und eine Methode bereitstellt, um diesen Aufruf auszulösen.

Hier ist ein Beispiel:

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

  • was bietet eine Methode, um den Anruf auszulösen? der Delegierte? wie? Funktionszeiger?

    – Dollarschein

    6. März 2012 um 11:04 Uhr

  • Die Delegate-Klasse bietet eine Methode wie Execute() die den Funktionsaufruf für das Objekt auslösen, das der Delegat umschließt.

    – Grimm der Opiner

    6. März 2012 um 11:15 Uhr

  • Anstelle von Execute würde ich in Ihrem Fall dringend empfehlen, den Aufrufoperator zu überschreiben – void CCallback::operator()(int). Der Grund dafür liegt in der generischen Programmierung, da erwartet wird, dass aufrufbare Objekte wie eine Funktion aufgerufen werden. o.Execute(5) wäre inkompatibel, aber o(5) würde gut als aufrufbares Template-Argument passen. Diese Klasse könnte auch verallgemeinert werden, aber ich nehme an, Sie haben sie der Kürze halber einfach gehalten (was gut ist). Abgesehen von diesem Nitpick ist dies eine sehr nützliche Klasse.

    – David Peterson

    10. September 2014 um 15:19 Uhr

Benutzer-Avatar
BitTickler

Die Notwendigkeit von C++-Delegatimplementierungen ist eine lang anhaltende Verlegenheit für die C++-Community. Jeder C++-Programmierer würde sie gerne haben, also verwenden sie sie schließlich trotz der Tatsachen, dass:

  1. std::function() verwendet Heap-Operationen (und ist für ernsthafte eingebettete Programmierung unerreichbar).

  2. Alle anderen Implementierungen machen Zugeständnisse in Richtung Portabilität oder Standardkonformität in größerem oder geringerem Maße (bitte überprüfen Sie dies, indem Sie die verschiedenen Delegiertenimplementierungen hier und auf codeproject überprüfen). Ich habe noch keine Implementierung gesehen, die keine wilden reinterpret_casts verwendet, verschachtelte Klassen “Prototypen”, die hoffentlich Funktionszeiger der gleichen Größe wie die vom Benutzer übergebenen erzeugen, Compiler-Tricks wie zuerst vorwärts deklarieren, dann typedef und dann erneut deklarieren, diesmal von einer anderen Klasse oder ähnlichen zwielichtigen Techniken erben. Obwohl es eine großartige Leistung für die Implementierer ist, die das gebaut haben, ist es immer noch ein trauriges Zeugnis dafür, wie sich C++ entwickelt.

  3. Nur selten wird darauf hingewiesen, dass seit über 3 C++-Standardrevisionen Delegaten nicht richtig angesprochen wurden. (Oder das Fehlen von Sprachfeatures, die eine unkomplizierte Delegiertenimplementierung ermöglichen.)

  4. Mit der Art und Weise, wie C++11-Lambda-Funktionen vom Standard definiert werden (jedes Lambda hat einen anonymen, anderen Typ), hat sich die Situation nur in einigen Anwendungsfällen verbessert. Aber für den Anwendungsfall der Verwendung von Delegaten in (DLL-)Bibliotheks-APIs, Lambdas allein sind immer noch nicht nutzbar. Die übliche Technik hier besteht darin, das Lambda zuerst in eine std::function zu packen und es dann über die API zu übergeben.

  • Ich habe eine Version von Elbert Mais generischen Callbacks in C++11 erstellt, die auf anderen Ideen basiert, die an anderer Stelle zu sehen sind, und es scheint, dass die meisten Probleme, die Sie in 2) anführen, geklärt sind. Das einzige verbleibende nörgelnde Problem für mich ist, dass ich mit einem Makro im Client-Code feststecke, um die Delegaten zu erstellen. Es gibt wahrscheinlich einen magischen Ausweg aus der Vorlage, den ich noch nicht gelöst habe.

    – kert

    25. August 2016 um 16:11 Uhr


  • Ich verwende std::function ohne Heap im eingebetteten System und es funktioniert immer noch.

    – prasad

    23. Oktober 2017 um 18:28 Uhr

  • std::function nicht stets dynamisch zuordnen

    – Leichtigkeitsrennen im Orbit

    13. Juli 2019 um 14:56 Uhr

  • Diese Antwort ist eher ein Scherz als eine tatsächliche Antwort

    – Leichtigkeitsrennen im Orbit

    13. Juli 2019 um 14:57 Uhr

Benutzer-Avatar
Dollarscheibe

Ganz einfach, ein Delegat bietet Funktionen dafür, wie ein Funktionszeiger funktionieren SOLLTE. Es gibt viele Einschränkungen für Funktionszeiger in C++. Ein Delegat verwendet hinter den Kulissen ein bösartiges Template, um ein funktionszeigerartiges Ding der Template-Klasse zu erstellen, das so funktioniert, wie Sie es vielleicht möchten.

dh – Sie können sie so einstellen, dass sie auf eine bestimmte Funktion zeigen, und Sie können sie herumreichen und anrufen, wann und wo immer Sie möchten.

Hier gibt es einige sehr gute Beispiele:

Eine Option für Delegaten in C++, die hier nicht anderweitig erwähnt wird, besteht darin, dies im C-Stil mit einer Funktion ptr und einem Kontextargument zu tun. Dies ist wahrscheinlich das gleiche Muster, das viele, die diese Frage stellen, zu vermeiden versuchen. Das Muster ist jedoch portabel, effizient und kann in eingebettetem und Kernel-Code verwendet werden.

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

Benutzer-Avatar
nmserve

Windows-Runtime-Äquivalent eines Funktionsobjekts in Standard-C++. Man kann die ganze Funktion als Parameter verwenden (eigentlich ist das ein Funktionszeiger). Es wird hauptsächlich in Verbindung mit Veranstaltungen verwendet. Der Delegat stellt einen Vertrag dar, den Ereignishandler weitgehend erfüllen. Es erleichtert, wie ein Funktionszeiger arbeiten kann.

1013250cookie-checkWas ist ein C++-Delegat?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy