Qu'est-ce qu'un délégué C ++?

147

Quelle est l'idée générale d'un délégué en C ++? Quels sont-ils, comment sont-ils utilisés et à quoi servent-ils?

J'aimerais d'abord en apprendre davantage sur eux dans une «boîte noire», mais un peu d'informations sur les tripes de ces choses serait également très bien.

Ce n'est pas du C ++ à l'état pur ou le plus propre, mais je remarque que la base de code sur laquelle je travaille en a en abondance. J'espère les comprendre suffisamment pour pouvoir les utiliser et ne pas avoir à me plonger dans l'horrible horrible modèle imbriqué.

Ces deux articles de The Code Project expliquent ce que je veux dire, mais pas particulièrement succinctement:

SirYakalot
la source
4
Parlez-vous de C ++ managé sous .NET?
dasblinkenlight
2
Avez-vous regardé la page Wikipedia sur la délégation ?
Matthijs Bierman
5
delegaten'est pas un nom courant dans le langage C ++. Vous devez ajouter des informations à la question pour inclure le contexte dans lequel vous l'avez lue. Notez que bien que le modèle puisse être commun, les réponses peuvent différer si vous parlez de délégué en général, ou dans le contexte de C ++ CLI ou de toute autre bibliothèque qui a une implémentation particulière de délégué .
David Rodríguez - dribeas
6
attendre 2 ans
?;
6
Toujours en attente ...
Grimm The Opiner

Réponses:

173

Vous avez un nombre incroyable de choix pour obtenir des délégués en C ++. Voici ceux qui me sont venus à l'esprit.


Option 1: foncteurs:

Un objet fonction peut être créé en implémentant 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: expressions lambda ( C ++ 11 uniquement)

// 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: pointeurs de fonction

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

Option 4: pointeur vers les fonctions membres (solution la plus rapide)

Voir Délégué Fast C ++ (sur The Code Project ).

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

Option 5: std :: fonction

(ou boost::functionsi votre bibliothèque standard ne le prend pas en charge). C'est plus lent, mais c'est le plus flexible.

#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: liaison (en utilisant std :: bind )

Permet de définir certains paramètres à l'avance, pratique pour appeler une fonction membre par exemple.

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: modèles

Acceptez n'importe quoi tant qu'il correspond à la liste d'arguments.

template <class FunctionT>
int DoSomething(FunctionT func)
{
    return func(3.14);
}
Jn
la source
2
Grande liste, +1. Cependant, seuls deux comptent vraiment comme délégués ici - capturer les lambdas et l'objet retourné std::bind, et les deux font vraiment la même chose, sauf que les lambdas ne sont pas polymorphes dans le sens où ils peuvent accepter différents types d'arguments.
Xeo
1
@JN: Pourquoi recommandez-vous de ne pas utiliser de pointeurs de fonction mais semble-t-il accepter d'utiliser les pointeurs de méthodes membres? Ils sont tout simplement identiques!
Matthieu M.
1
@MatthieuM. : juste point. Je considérais les pointeurs de fonction comme un héritage, mais ce n'est probablement que mon goût personnel.
JN
1
@Xeo: mon idée de délégué est plutôt empirique. Peut-être que je mélange trop d'objet de fonction et de délégué (blâmez mon ancienne expérience C #).
JN
2
@SirYakalot Quelque chose qui se comporte comme une fonction, mais qui peut tenir état en même temps et peut être manipulé comme n'importe quelle autre variable. Une utilisation par exemple est de prendre une fonction qui prend deux paramètres et de forcer le 1er paramètre à avoir une certaine valeur en créant une nouvelle fonction avec un seul paramètre ( bindingdans ma liste). Vous ne pouvez pas y parvenir avec des pointeurs de fonction.
JN
37

Un délégué est une classe qui encapsule un pointeur ou une référence vers une instance d'objet, une méthode membre de la classe de cet objet à appeler sur cette instance d'objet et fournit une méthode pour déclencher cet appel.

Voici un exemple:

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 l'Opiner
la source
qui fournit une méthode pour déclencher l'appel? le délégué? Comment? pointeur de fonction?
SirYakalot
La classe de délégué proposera une méthode comme celle Execute()qui déclenchera l'appel de fonction sur l'objet encapsulé par le délégué.
Grimm The Opiner
4
Au lieu d'Execute, je recommande fortement dans votre cas de remplacer l'opérateur d'appel - void CCallback :: operator () (int). La raison en est que dans la programmation générique, les objets appelables sont censés être appelés comme une fonction. o.Execute (5) serait incompatible, mais o (5) conviendrait parfaitement comme argument de modèle appelable. Cette classe pourrait également être généralisée, mais je suppose que vous l'avez gardée simple par souci de concision (ce qui est une bonne chose). En dehors de ce pinaillage, c'est une classe très utile.
David Peterson
18

Le besoin d'implémentations de délégués C ++ est un embarras durable pour la communauté C ++. Tous les programmeurs C ++ aimeraient les avoir, donc ils les utilisent finalement malgré les faits que:

  1. std::function() utilise des opérations de tas (et est hors de portée pour une programmation embarquée sérieuse).

  2. Toutes les autres implémentations font des concessions soit sur la portabilité, soit sur la conformité standard à des degrés plus ou moins élevés (veuillez vérifier en inspectant les différentes implémentations de délégués ici et sur codeproject). Je n'ai pas encore vu d'implémentation qui n'utilise pas de reinterpret_casts sauvages, des "prototypes" de classe imbriquée qui, espérons-le, produisent des pointeurs de fonction de la même taille que celle transmise par l'utilisateur, des astuces de compilateur comme first forward declare, puis typedef puis déclarez à nouveau, cette fois héritant d'une autre classe ou de techniques louches similaires. Bien que ce soit une grande réussite pour les implémenteurs qui ont construit cela, c'est toujours un triste témoignage sur la façon dont le C ++ évolue.

  3. Il est rarement souligné que maintenant, sur 3 révisions standard C ++, les délégués n'étaient pas correctement adressés. (Ou le manque de fonctionnalités de langage permettant des implémentations de délégué simples.)

  4. Avec la façon dont les fonctions lambda C ++ 11 sont définies par la norme (chaque lambda a un type anonyme et différent), la situation ne s'est améliorée que dans certains cas d'utilisation. Mais pour le cas d'utilisation de l'utilisation de délégués dans les API de bibliothèque (DLL), les lambdas seuls ne sont toujours pas utilisables. La technique courante ici consiste à emballer d'abord le lambda dans une fonction std ::, puis à le transmettre à travers l'API.

BitTickler
la source
J'ai fait une version des rappels génériques d'Elbert Mai en C ++ 11 sur la base d'autres idées vues ailleurs, et cela semble effacer la plupart des problèmes que vous citez dans 2). Le seul problème persistant pour moi est que je suis bloqué en utilisant une macro dans le code client pour créer les délégués. Il y a probablement un moyen magique de sortir de cela que je n'ai pas encore résolu.
kert
3
J'utilise std :: function sans tas dans le système embarqué et cela fonctionne toujours.
prasad
1
std::functionn'alloue pas toujours dynamiquement
Courses de légèreté en orbite
3
Cette réponse est plus une diatribe qu'une vraie réponse
Courses de légèreté en orbite
8

Très simplement, un délégué fournit des fonctionnalités sur la façon dont un pointeur de fonction DEVRAIT fonctionner. Il existe de nombreuses limitations des pointeurs de fonction en C ++. Un délégué utilise une certaine méchanceté de modèle dans les coulisses pour créer un objet de type pointeur de fonction de classe de modèle qui fonctionne comme vous le souhaitez.

c'est-à-dire - vous pouvez les configurer pour qu'ils pointent sur une fonction donnée et vous pouvez les faire circuler et les appeler quand et où vous le souhaitez.

Il y a de très bons exemples ici:

SirYakalot
la source
4

Une option pour les délégués en C ++ qui n'est pas mentionnée autrement ici est de le faire en style C en utilisant une fonction ptr et un argument de contexte. C'est probablement le même schéma que beaucoup de personnes qui posent cette question essaient d'éviter. Mais, le modèle est portable, efficace et utilisable dans le code embarqué et du noyau.

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
la source
1

Windows Runtime équivalent d'un objet fonction en C ++ standard. On peut utiliser toute la fonction comme paramètre (en fait c'est un pointeur de fonction). Il est principalement utilisé en conjonction avec des événements. Le délégué représente un contrat que les gestionnaires d'événements remplissent beaucoup. Cela facilite le fonctionnement d'un pointeur de fonction.

nerf
la source