57 votes

Comment fonctionne le paramètre de gabarit de std::function? (implémentation)

Sur la page d'accueil de Bjarne Stroustrup (C++11 FAQ) :

struct X { int foo(int); };

std::function f;
f = &X::foo; //pointeur vers membre

X x;
int v = f(&x, 5); //appel de X::foo() pour x avec 5

Comment ça fonctionne ? Comment std::function appelle une fonction membre foo ?

Le paramètre du modèle est int(X*, int), &X::foo est-il converti du pointeur vers fonction membre en un pointeur vers fonction non-membre ?!

(int(*)(X*, int))&X::foo //cast (int(X::*)(int) en (int(*)(X*, int))

Pour clarifier : Je sais que nous n'avons pas besoin de caster de pointeur pour utiliser std::function, mais je ne comprends pas comment std::function gère cette incompatibilité entre un pointeur vers fonction membre et un pointeur vers fonction non-membre au niveau interne. Je ne sais pas comment la norme nous permet d'implémenter quelque chose comme std::function !

1 votes

Si vous faites référence à une autre page web. Veuillez toujours fournir le lien.

1 votes

La page est ici : www2.research.att.com/~bs/C++0xFAQ.html#std-function. Bjarne affirme que c'est légal, mais ne mentionne pas les mécanismes de fonctionnement (qu'il s'agisse de modèles astucieux ou d'une sorte de nouvelle conversion intégrée).

35voto

ccSadegh Points 2885

Après avoir reçu de l'aide d'autres réponses et commentaires, et avoir lu le code source GCC et le standard C++11, j'ai découvert qu'il est possible d'analyser un type de fonction (son type de retour et ses types d'arguments) en utilisant la spécialisation partielle de modèles et la surcharge de fonctions.

Voici un exemple simple (et incomplet) pour implémenter quelque chose comme std::function:

template class Fonction { };

// Analyser le type de fonction
template
class Fonction {
    union Pointeurs {
        Res (*func)(Obj*, ArgTypes...);
        Res (Obj::*mem_func)(ArgTypes...);
    };

    typedef Res Rappel(Pointeurs&, Obj&, ArgTypes...);

    Pointeurs ptrs;
    Rappel* rappel;

    static Res appeler_func(Pointeurs& ptrs, Obj& obj, ArgTypes... args) {
        return (*ptrs.func)(&obj, args...);
    }

    static Res appeler_mem_func(Pointeurs& ptrs, Obj& obj, ArgTypes... args) {
        return (obj.*(ptrs.mem_func))(args...);
    }

  public:

    Fonction() : rappel(0) { }

    // Analyser le type de fonction
    Fonction(Res (*func)(Obj*, ArgTypes...)) {
        ptrs.func = func;
        rappel = &appeler_func;
    }

    // Analyser le type de fonction
    Fonction(Res (Obj::*mem_func)(ArgTypes...)) {
        ptrs.mem_func = mem_func;
        rappel = &appeler_mem_func;
    }

    Fonction(const Fonction& fonction) {
        ptrs = fonction.ptrs;
        rappel = fonction.rappel;
    }

    Fonction& operator=(const Fonction& fonction) {
        ptrs = fonction.ptrs;
        rappel = fonction.rappel;
        return *this;
    }

    Res operator()(Obj& obj, ArgTypes... args) {
        if(rappel == 0) throw 0; // throw an exception
        return (*rappel)(ptrs, obj, args...);
    }
};

Utilisation:

#include 

struct Rigolo {
    void imprimer(int i) {
        std::cout << "void (Rigolo::*)(int): " << i << std::endl;
    }
};

void imprimer(Rigolo* rigolo, int i) {
    std::cout << "void (*)(Rigolo*, int): " << i << std::endl;
}

int main(int argc, char** argv) {
    Rigolo rigolo;
    Fonction wmw;

    wmw = &Rigolo::imprimer; // void (Rigolo::*)(int)
    wmw(rigolo, 10); // void (Rigolo::*)(int)

    wmw = &imprimer; // void (*)(Rigolo*, int)
    wmw(rigolo, 8); // void (*)(Rigolo*, int)

    return 0;
}

3 votes

On détesterait penser qu'il y a une déclaration switch enfouie à l'intérieur de ce code.

0 votes

@Martin York: Merci, je l'ai changé.

3 votes

std::function accepte également des objets de fonction, en plus des pointeurs de fonction et des pointeurs de fonction membres.

4voto

Loki Astari Points 116129

Comment cela se fait (je crois) est laissé indéfini (mais je n'ai pas de copie de la norme ici).

Mais étant donné toutes les différentes possibilités qui doivent être couvertes, j'ai le sentiment que déchiffrer la définition exacte de son fonctionnement serait vraiment difficile : donc je ne vais pas essayer.

Mais je pense que vous aimeriez savoir comment fonctionnent les foncteurs et ils sont relativement simples. Voici donc un exemple rapide.

Foncteurs:

Ce sont des objets qui agissent comme des fonctions.
Ils sont très utiles dans le code de modèle car ils permettent souvent d'utiliser des objets ou des fonctions de manière interchangeable. La grande chose à propos des foncteurs cependant est qu'ils peuvent contenir un état (une sorte de fermeture de pauvre).

struct X
{
     int operator()(int x) { return doStuff(x+1);}
     int doStuff(int x)    { return x+1;}
};

X   x;  // Vous pouvez maintenant utiliser x comme une fonction
int  a = x(5);

Vous pouvez utiliser le fait que le foncteur contient un état pour stocker des choses comme des paramètres ou les objets ou le pointeur vers des méthodes membres (ou toute combinaison de ceux-ci).

struct Y // Tient un pointeur de fonction membre
{
    int (X::*member)(int x);
    int operator(X* obj, int param) { return (obj->*member)(param);}
};
X  x;
Y  y;
y.member = &X::doStuff;
int a = y(&x,5);

Ou allez même plus loin et liez des paramètres. Maintenant, tout ce que vous devez fournir est l'un des paramètres.

struct Z
{
    int (X::*member)(int x);
    int  param;
    Z(int (X::*m)(int), int p) : member(m), param(p) {}

    int operator()(X* obj)  { return (obj->*member)(param);}
    int operator()(X& obj)  { return (obj.*member)(param);}
};

Z z(&X::doStuff,5);

X x;
int a = z(x);

0 votes

Merci pour les informations, même si je sais ce qu'est un foncteur (objet fonction), et j'ai essayé de savoir : "Comment la fonction std :: function tient/appele un pointeur de fonction membre/non-membre?" et je n'ai pas pu.

0 votes

Il utilise probablement tout un ensemble de spécialisations de modèle. Mais détenir un pointeur de fonction n'est pas différent de détenir un pointeur de méthode, ce sont juste les types qui sont différents.

3voto

alfC Points 881

Pour répondre à la question dans le titre. Le paramètre que std::function utilise est un joli tour de passe-passe pour passer de nombreux paramètres de type en tant qu'unique paramètre de modèle. Ces arguments étant les types d'arguments et le type de retour d'une fonction.

Il s'avère que std::function tente d'effacer le type d'un foncteur général mais ce n'est qu'une coïncidence.

En fait, il fut un temps où il y avait des compilateurs qui n'acceptaient pas de tels tours et le prédécesseur de boost::function avait une syntaxe portable par laquelle tous les paramètres pouvaient être passés séparément :

Syntaxe préférée

boost::function sum_avg;

Syntaxe portable

boost::function4 sum_avg;

https://www.boost.org/doc/libs/1_68_0/doc/html/function/tutorial.html#id-1.3.16.5.4

C'est ainsi que fonctionnent les paramètres de modèle de std::function, en fin de compte c'est juste un tour pour faire ressembler à un appel de fonction un grand nombre de paramètres. Les pointeurs de fonction vers ce type de fonction ne sont pas nécessairement impliqués dans la classe.

2voto

Tomek Points 2742

G++ semble avoir une union qui peut contenir un pointeur de fonction, un pointeur de membre ou un pointeur void qui pointe probablement vers un foncteur. Ajoutez des surcharges qui indiquent correctement quel membre de l'union est valide et des conversions importantes en un "soup" puis ça fonctionne...

0 votes

Merci, je crois que le fait de convertir un pointeur de fonction membre en un pointeur de fonction non membre est un comportement non défini! et en utilisant un union, aucun cast n'est nécessaire. je devrais le tester. mais comment les membres de cet union sont-ils détectables par un seul paramètre de modèle (nous spécifions simplement un type de fonction pour std::function)?

0 votes

"pointeur void qui pointe probablement vers un pointeur qui pointe probablement vers un foncteur" devrait également gérer en quelque sorte ce foncteur en mémoire ?

2voto

Puppy Points 90818

Ce ne sont pas des pointeurs de fonction. C'est à ça que sert std::function. Il enveloppe tout type d'appelable que vous lui donnez. Vous devriez jeter un œil à boost::bind- il est souvent utilisé pour rendre les pointeurs de fonction membres appelables comme (this, args).

4 votes

Sa question est toujours valide, cependant. L'instanciation std::function est paramétrée avec int (X*, int), qui n'est pas le même type que le type de &X::foo, qui lui est assigné. Même s'il est clair comment vous pourriez invoquer ce dernier en fonction des arguments du premier, du point de vue du compilateur, ces types ne sont pas intrinsèquement liés, il n'est donc pas évident de comprendre comment cela est autorisé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X