IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Collection & callbacks


Sujet :

Langage C++

  1. #1
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut Collection & callbacks
    Bonsoir,

    Je suis à la recherche de quelques indications pour réaliser l'implémentation d'une collection de callbacks un peu particulière.

    Je compte réaliser une classe utilisant une std::map dans laquelle je pourrais stocker des fonctions.
    Le but est de les appeler lors du retour d'échanges asynchrones avec un serveur distant, typiquement en HTTP.
    Ces fonctions seraient enregistrées sous divers identifiants tels que "success", "error", "timeout"... pour être appelées dans ces différents cas sous cette forme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    int retour = clbk.call("success", arg1, arg2, ..., argN);
    Néanmoins, je rencontre 3 problèmes majeurs, sans également savoir si la formalisation de mon besoin est compatible avec des langages comme le c++.
    La 1ere chose est de savoir quel type C++ utiliser pour représenter une fonction de rappel. Il semble y avoir boost::function mais aussi d'autre types en c++ natif. Lequel choisir?

    Le second problème, il ne sera pas des moindres, est la manière dont je compte appeler mes fonctions : avec un nombre et un type d'argument variable (cf le code ci-dessus). Est-ce possible en C++?
    Il n'est pas exclus que tous mes callbacks "success" puissent avoir la même signature, mais deux callbacks "success" et "error" auront forcément des différences. Utiliser ma méthode call() pour tous les appeler n'est peut-être pas implémentable.

    Le 3ième reste le type de retour. Si call() renvoie lui-même le retour de la fonction appelée et qu'il est sans cesse différent je risque d'être un peu coincé.

    Existe-t-il une solution à ce type de problème?
    Je connais l'exemple de printf, qui peut admettre un nombre variables d'arguments de différents type sans savoir comment déclarer une telle méthode de classe.

    Merci par avance pour vos éclairages éventuels.

  2. #2
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Hello,

    Citation Envoyé par fanfouer Voir le message
    La 1ere chose est de savoir quel type C++ utiliser pour représenter une fonction de rappel. Il semble y avoir boost::function mais aussi d'autre types en c++ natif. Lequel choisir?
    Il y a boost::function et std::function (et probablement d'autres, mais ce sont surement les plus courant)
    Si tu peux utiliser C++11, les std::function c'est cool.

    Citation Envoyé par fanfouer Voir le message
    Le second problème, il ne sera pas des moindres, est la manière dont je compte appeler mes fonctions : avec un nombre et un type d'argument variable (cf le code ci-dessus). Est-ce possible en C++?
    Il n'est pas exclus que tous mes callbacks "success" puissent avoir la même signature, mais deux callbacks "success" et "error" auront forcément des différences. Utiliser ma méthode call() pour tous les appeler n'est peut-être pas implémentable.
    Oui c'est possible, après si tu distribue les appels à d'autres threads pour les exécuter plus tards, faut regarder du coté de std::future (ou de l'équivalent boost)

    Citation Envoyé par fanfouer Voir le message
    Le 3ième reste le type de retour. Si call() renvoie lui-même le retour de la fonction appelée et qu'il est sans cesse différent je risque d'être un peu coincé.
    Tant que tu sais à l'avance quelle fonction retourne quoi, ça devrait aller.
    (Ce qui peut poser problème si tu reçois une chaîne de caractères et que tu exécutes la fonction correspondante)

    Il faut que tu crée une classe non template qui contiendra une std::function ; non template pour pouvoir créer une map de ce type, ici l'héritage permet ça :
    Une classe de base non template, héritée par une classe template qui contiendra la std::function.
    Les template variadic permettent un nombre quelconque de paramètres.

    Après un dynamic_cast permet de tester que les types et le nombre des arguments fournis correspondent au prototype de la fonction.

    Au final ça donne un truc du genre
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    #include <functional>
    #include <iostream>
    #include <string>
    #include <map>
    #include <memory>
     
    class FctBase;
     
    template <class Ret, class... Args>
    class Fct;
     
    class FctBase {
     
    	// oui c'est sale :/
    	virtual void virtualFctPourDynamicCast() { }
     
    public:
    	template<class Ret, class... Args>
    	Ret doJob(Args... args) const {
    		const Fct<Ret, Args...> *_this = dynamic_cast<const Fct<Ret, Args...>*>(this);
    		if(!_this) {
    			throw std::exception();
    		}
     
    		return _this->call(args...);
    	}	
    };
     
    template <class Ret, class... Args>
    class Fct: public FctBase {
     
    	const std::function<Ret(Args...)> m_fct;
     
    public:
     
    	Fct(const std::function<Ret(Args...)> fct):
    		m_fct(fct)
    	{ }
     
    	~Fct() { }
     
    	Ret call(Args... args) const {
    		return m_fct(args...);
    	};
     
    };
     
     
    template <class Ret, class... Args>
    Ret call(const Fct<Ret, Args...> *fct, Args... args) {
    	return fct->call(args...);
    } 
     
    int main(int argc, char** argv) {
     
    	typedef std::unique_ptr<FctBase> ptr;
     
    	std::map<std::string, ptr> fctMap;
     
    	auto fct1 = [](int i, int j)->int { return i+j; };
    	auto fct2 = [](char c)->float { return c/255.f; };
     
    	// insert
    	fctMap.insert(std::make_pair("fct1", ptr(new Fct<int, int, int>(fct1))));
    	fctMap.insert(std::make_pair("fct2", ptr(new Fct<float, char>(fct2))));
     
    	// recherche et exec si trouvé	
    	auto itFct1 = fctMap.find("fct1");
    	if(itFct1 != fctMap.end()) {
    		try {
    			int ret = itFct1->second->doJob<int, int, int>(1, 2);
    			std::cout << "fct1(1, 2) = " << ret << std::endl;
    		}
    		catch(...) {
    			std::cout << "fct1(1, 2) fail\n";
    		}		
    	}
     
    	auto itFct2 = fctMap.find("fct2");
    	if(itFct2 != fctMap.end()) {
    		try {
    			float ret = itFct2->second->doJob<float, char>('h');
    			std::cout << "fct2('h') = " << ret << std::endl;
    		}
    		catch(...) {
    			std::cout << "fct2('h') fail\n";
    		}
    	}
     
    	// mauvais appel
    	if(itFct1 != fctMap.end()) {
    		try {
    			int ret = itFct1->second->doJob<int, char>('h');
    			std::cout << "fct1('h') = " << ret << std::endl;
    		}
    		catch(...) {
    			std::cout << "fct1('h') fail\n";
    		}
    	}
     
    	return 0;
    }
    sortie:
    fct1(1, 2) = 3
    fct2('h') = 0.407843
    fct1('h') fail

    Code pas spécialement propre mais ça donne une idée.
    D'ailleurs t'aura surement besoin d'une std::multimap vu que plusieurs fonctions ont le même nom.

  3. #3
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Il est intéressant dans ce genre de cas de différencier deux types d'arguments pour tes fonctions :
    - Ceux qui ont une valeur qui peut être connue quand on enregistre la fonction dans la map
    - Ceux qui ont une valeur uniquement lorsqu'on veut appeler la fonction

    Assez souvent, on se retrouve avec un nombre variable d'arguments dans la première catégorie, mais un nombre identique dans la seconde. Il est alors possible de mettre des [boost/std]::fonction dans le conteneur qui prennent uniquement les arguments variables, et d'adapter les fonctions ayant trop d'arguments vers ces xxx::function à l'aide de [boost/std]::bind, de lambdas, ou autres.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  4. #4
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Bonsoir,

    merci pour vos réponses

    Citation Envoyé par Iradrille Voir le message
    Il y a boost::function et std::function (et probablement d'autres, mais ce sont surement les plus courant)
    Si tu peux utiliser C++11, les std::function c'est cool.
    A condition que Visual Studio supporte cette fonctionnalité de C++11 je suis d'accord pour utiliser la STL plutôt que boost

    Oui c'est possible, après si tu distribue les appels à d'autres threads pour les exécuter plus tards, faut regarder du coté de std::future (ou de l'équivalent boost)
    Normalement ça n'est pas prévu. Il faut uniquement tenir compte d'appels dans le même thread que la routine asynchrone (peut-être était-ce ton idée? Je ne prévois pas de threader particulièrement l'appel au callback).

    Tant que tu sais à l'avance quelle fonction retourne quoi, ça devrait aller.
    (Ce qui peut poser problème si tu reçois une chaîne de caractères et que tu exécutes la fonction correspondante)
    Du point de vue du déclencheur des callbacks ca n'est pas le cas (mine de rien).
    Bien souvent, mes callbacks retournent void mais sait-on jamais ce qui peut se présenter demain.

    Il faut que tu crée une classe non template qui contiendra une std::function ; non template pour pouvoir créer une map de ce type, ici l'héritage permet ça :
    Une classe de base non template, héritée par une classe template qui contiendra la std::function.
    Les template variadic permettent un nombre quelconque de paramètres.
    Je comprends très bien le besoin de l'extension pour obtenir à un moment un type non template à manipuler.
    Cependant pourquoi ne pas stocker un std::function directement dans la map?

    En fait, une seule fonction portera un nom donné dans la collection.
    Je peux avoir deux callback "success" mais ils seront dans deux collections séparées. Nul besoin d'user de multimap ici.

    Cela permet-il de simplifier quelque peu l'implémentation proposée?

    Après un dynamic_cast permet de tester que les types et le nombre des arguments fournis correspondent au prototype de la fonction.
    Oui, le dynamic_cast peut permettre de vérifier que la bonne signature est fournie sans pour autant permettre un quelconque choix puisqu'il n'y a qu'une seule signature pour chaque nom de callbacks.


    Au niveau du code, cela correspond globalement à ce que j'ai dans l'idée si ce n'est que je pense faire quelque chose de plus intégré au niveau de l'appel.
    La classe qui gèrerait tout mes callbacks peut avoir deux principales méthodes :

    - insert (std::string clbk_name, std::function clbk_func) : pour insérer les callback dans la collection

    - call (std::string clbk_name, Args... args); : pour appeler le callback.

    Il est intéressant dans ce genre de cas de différencier deux types d'arguments pour tes fonctions :
    - Ceux qui ont une valeur qui peut être connue quand on enregistre la fonction dans la map
    - Ceux qui ont une valeur uniquement lorsqu'on veut appeler la fonction
    Je ne pense pas.
    Si ces arguments peuvent obtenir une valeur fixe dès l'enregistrement du callback (soit bien avant l'appel de la routine qui va s'en servir), c'est que ce sont des "constantes" non?
    Enfin, j'en suis peut-être encore trop au stade javascript et son scope "sables mouvants". Surement qu'en C++ il faudra prévoir ce genre de valeurs, qui ne sont pas fournies par la routine et qui dépendent du contexte d’exécution.
    Je pense que si je peux m'en passer ce ne sera pas de refus, mais c'est une question de design

    Quel est votre avis?

    Bonne soirée!

  5. #5
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par fanfouer Voir le message
    A condition que Visual Studio supporte cette fonctionnalité de C++11 je suis d'accord pour utiliser la STL plutôt que boost
    std::function est supporté par VS2012, par contre pour les templates variadic, il te faudra le CTP de novembre

    Citation Envoyé par fanfouer Voir le message
    Je comprends très bien le besoin de l'extension pour obtenir à un moment un type non template à manipuler.
    Cependant pourquoi ne pas stocker un std::function directement dans la map?
    Car justement std::function est une classe template, si toutes tes fonctions ont le même prototype alors oui tu peux.
    Par exemple si elles prennent toutes un int en paramètre et retournent rien, tu pourrais déclarer map<string, function<void(int)> > maMap;. Mais lorsque les prototypes sont différents, tu ne peux plus donner les arguments template de std::function
    Citation Envoyé par fanfouer Voir le message
    En fait, une seule fonction portera un nom donné dans la collection.
    Je peux avoir deux callback "success" mais ils seront dans deux collections séparées. Nul besoin d'user de multimap ici.

    Cela permet-il de simplifier quelque peu l'implémentation proposée?
    Dans ce que je proposais, je prenais pas en compte le fait que 2 fonctions aient le même nom (je disais juste qu'il fallait le faire :p). Mais oui ça simplifie l'implémentation, pas besoin d'itérer sur une liste de fonctions possible.

  6. #6
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Merci pour cette réponse.

    Citation Envoyé par Iradrille Voir le message
    std::function est supporté par VS2012, par contre pour les templates variadic, il te faudra le CTP de novembre
    Excellent, je l'ai déjà installé.

    Car justement std::function est une classe template
    Oui, très juste. Je comprends la nécessité d'avoir un wrapper un peu sophistiqué.

    Dans ce que je proposais, je prenais pas en compte le fait que 2 fonctions aient le même nom (je disais juste qu'il fallait le faire :p). Mais oui ça simplifie l'implémentation, pas besoin d'itérer sur une liste de fonctions possible.
    D'accord. Je verrai donc ça demain pour l'implémentation et je reviendrai par ici donner le résultat.

  7. #7
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Pour compléter la réponse de Loïc, si tu veux être générique sur tes callbacks, elles doivent un minimum répondre à la même signature (tu peux définir plusieurs « types » éventuellement). Sinon, cela veut dire que tu as besoin de savoir quelle callback tu appelles pour savoir quels arguments lui passer, et toute l’extensibilité run-time de ton système est cassée.

    Après, libre à tes callbacks d’ignorer certains paramètres qui leurs sont passés.

    Idem pour la valeur de retour : elle ne sera exploitée que par l’exécutant de la callback, pas celui qui l’a enregistrée. De fait, elle devrait donc avoir une sémantique fixe pour toutes les callbacks. Une callback doit respecter un certain prototype, qui est défini par ton manager de callbacks.

  8. #8
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Pour compléter la réponse de Loïc, si tu veux être générique sur tes callbacks, elles doivent un minimum répondre à la même signature (tu peux définir plusieurs « types » éventuellement). Sinon, cela veut dire que tu as besoin de savoir quelle callback tu appelles pour savoir quels arguments lui passer, et toute l’extensibilité run-time de ton système est cassée.
    Pas forcément, tant que celui qui exéctute la callback fourni les paramètres (reste le problème du type des paramètre puisque qu’apparemment ça vient de javascript), ça ne devrait pas poser de soucis ?

    Citation Envoyé par white_tentacle Voir le message
    Idem pour la valeur de retour : elle ne sera exploitée que par l’exécutant de la callback, pas celui qui l’a enregistrée. De fait, elle devrait donc avoir une sémantique fixe pour toutes les callbacks. Une callback doit respecter un certain prototype, qui est défini par ton manager de callbacks.
    La oui par contre, à moins que celui qui exécute la callback donne le type de retour, le type sera inconnu et devra donc être le même pour toutes les callback pour éviter ça.

  9. #9
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Quelques questions sur le code à proprement dit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    // oui c'est sale :/
    virtual void virtualFctPourDynamicCast() { }
    A quoi cette fonction virtuelle sert-elle?
    Pourquoi est-ce si sale, surtout si on ne s'en sert pas par la suite?

    De plus, ne serait-il pas possible de fusionner FctBase::doJob et Fct::call()?
    Je vois bien que doJob fait le dynamic_cast pour ensuite pouvoir en gros appeler un "call" correct avec les bons types.
    D'un autre côté ca reste un niveau de plus dans ma pile d'appels.
    Si on peut tout faire en même temps je ne m'en porterais que mieux

    J'ai également des erreurs de compilation autour de:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    public: template <class Ret, class... Args> Ret doJob(Args... args) const;
    dans mon fichier FctBase.h :

    error C2332: 'class'*: nom de balise manquant
    error C2993: ''*: type non conforme pour le paramètre de modèle sans type '<unnamed-tag>'
    error C2143: erreur de syntaxe*: absence de ',' avant '...'
    error C2988: impossible de reconnaître la définition/déclaration de modèle
    error C2059: erreur de syntaxe*: 'const'
    A peu près idem dans le .cpp quand je tente la déclaration.

    Merci pour vos retours

  10. #10
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    La fonction virtuelle comme son nom l'indique sert à faire marcher le dynamic_cast, "error C2683: 'dynamic_cast' : 'FctBase' is not a polymorphic type" sans.
    C'est sale, car ya très certainement une erreur de conception quelques part pour que je sois obligé de rajouter cette fonction.

    Pour fusionner FctBase::doJob et Fct::call(), je ne pense pas que ce soit possible, mais les 2 fonctions seront probablement inline de toute façon.

    Les erreurs de compilation doivent venir du non support des templates variadic.
    (Sous VS2012 avec le ctp de'installé, propriétés du projet > ensemble d'outil de la plateforme (dans Général) > VC++ Compiler 2012 CTP)

    edit: Fonction template donc à implémenter dans le .h.

  11. #11
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Pour l'instant ça compile, nous verrons prochainement pour l’exécution

    Citation Envoyé par Iradrille Voir le message
    La fonction virtuelle comme son nom l'indique sert à faire marcher le dynamic_cast, "error C2683: 'dynamic_cast' : 'FctBase' is not a polymorphic type" sans.
    C'est sale, car ya très certainement une erreur de conception quelques part pour que je sois obligé de rajouter cette fonction.
    D'accord. Personnellement ca ne me gêne pas. Si quelqu'un à mieux, je pourrai modifier le code.

    Pour fusionner FctBase::doJob et Fct::call(), je ne pense pas que ce soit possible, mais les 2 fonctions seront probablement inline de toute façon.
    Oui en effet je pense avoir compris pourquoi.

    Les erreurs de compilation doivent venir du non support des templates variadic.
    (Sous VS2012 avec le ctp de'installé, propriétés du projet > ensemble d'outil de la plateforme (dans Général) > VC++ Compiler 2012 CTP)

    edit: Fonction template donc à implémenter dans le .h.
    Merci pour la modification, ça fonctionne mieux.

    J'ai indiqué la signature des fonctions template dans le .h, le reste du code est dans le cpp sans que je trouve d'erreur de compilation.
    Cela va-t-il en créer au runtime?

  12. #12
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par fanfouer Voir le message
    J'ai indiqué la signature des fonctions template dans le .h, le reste du code est dans le cpp sans que je trouve d'erreur de compilation.
    Cela va-t-il en créer au runtime?
    Ca compile car tu ne dois pas utiliser les fonctions, dès que tu les utiliseras, tu aura une erreur de link te disant que le corps de la fonction est introuvable.

    (Petite question HS: Même si ya des erreurs de syntaxe dans le corps d'une fonction template, VS ne relève pas d'erreur à la compilation tant que la fonction n'est pas instanciée.
    Quelqu'un sait ce que dit la norme à ce sujet ?)

  13. #13
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    (Petite question HS: Même si ya des erreurs de syntaxe dans le corps d'une fonction template, VS ne relève pas d'erreur à la compilation tant que la fonction n'est pas instanciée.
    Quelqu'un sait ce que dit la norme à ce sujet ?)
    Je ne pense pas qu'il s'agisse de norme ou spécificité VS. Les templates sont compilés uniquement pour les types utilisés réellement. Donc ton code template n'est pas compilé s'il n'est pas utilisé.
    Heureusement: à chaque déclaration template il faudrait qu'il compile pour tous les types existants ? Et si t'as prévu ton template pour marcher avec des nombres il devrait t'indiquer des erreurs parce que %= n'est pas défini pour ta class Character ?
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  14. #14
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Ca compile car tu ne dois pas utiliser les fonctions, dès que tu les utiliseras, tu aura une erreur de link te disant que le corps de la fonction est introuvable.
    D'accord.
    Je modifierai donc mon code pour que ça marche.

    En est-il de même pour les classes intégralement templates?
    J'ai des classes entières où toutes les méthodes sont précédées par template<A, B>.
    Ces classes doivent-elles êtres intégralement déclarées dans le .h?

    Une autre question parallèle, celle du "scope".
    En Javascript, les closures ont la possibilité d'accéder au éléments du scope existants à leur déclaration.
    Généralement, mes callbacks sont déclarés puis enregistrés dans la collection au sein d'une méthode de classe, il serait ensuite fort commode que le callback lui-même puisse accéder à l'instance courante de l'objet dans lequel il a été déclaré.

    Ça correspondrait aux arguments passés lors de l'enregistrement du callback, plus qu'à son exécution, évoqués par white_tentacle.

    Le code en JS :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    this.ma_methode_de_classe = function (){
       var INST = this;
       var clbk_set = new ClbkSet();
     
       var clbk = function (){
          INST.methode1(); // Je me sert de INST mais il n'a été fourni à aucun moment ni au callback, ni à la collection elle-même.
          INST.attr1 = "value";
       };
     
       clbk_set.put("success", clbk);
     
       // Le callback est enregistré et prêt à servir
    }

  15. #15
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Pas forcément, tant que celui qui exéctute la callback fourni les paramètres (reste le problème du type des paramètre puisque qu’apparemment ça vient de javascript), ça ne devrait pas poser de soucis ?
    Je suis peut-être passé à côté d’un truc, mais de ce que j’en ai compris :
    - on s’enregistre auprès d’un gestionnaire
    - le gestionnaire rappelle les callbacks quand certains évènements surviennent

    Soit tu as :
    - une liste d’évènements fixée à la compilation, avec des signatures de callbacks qui correspondent à chaque type d’évènement. Fonctionnel mais peu extensible
    - une liste d’évènements qui peut changer au runtime, avec des callbacks associées. Ton gestionnaire n’ayant pas connaissance des types d’évènements, il faut que la signature des callbacks soit identique. Sinon, tu n’as pas trop moyen de savoir avec quels arguments appeler la callback (sauf s’ils sont portés par l’évènement, auquel cas effectivement tu forwardes l’équivalent d’un eventdata qui peut être n’importe quoi)

    Dans l’idéal, je trouve la première solution meilleure, avec une solution à base de templates pour garder une certaine extensibilité à la compilation.

    Note : ça ressemble beaucoup à un gestionnaire de signaux/slots. Pourquoi ne pas en utiliser un tout fait (boost ou Qt par exemple) ?

    J'ai des classes entières où toutes les méthodes sont précédées par template<A, B>.
    Ces classes doivent-elles êtres intégralement déclarées dans le .h?
    Oui. Ou dans un fichier .tpp à côté que tu inclus à la fin de ton .h

    Généralement, mes callbacks sont déclarés puis enregistrés dans la collection au sein d'une méthode de classe, il serait ensuite fort commode que le callback lui-même puisse accéder à l'instance courante de l'objet dans lequel il a été déclaré.
    C’est ce que te suggérais Loïc avec boost::bind. Sinon, si tu es en C++11, tu peux utiliser des lambdas avec capture du this :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    [this](int arg1) { return this->x + arg1; }
    Attention : cela n’étend pas la durée de vie de ton this : tu es assuré d’un beau segfault si l’objet est détruit mais que la callback est appelée.

  16. #16
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    La fonction virtuelle comme son nom l'indique sert à faire marcher le dynamic_cast, "error C2683: 'dynamic_cast' : 'FctBase' is not a polymorphic type" sans.
    C'est sale, car ya très certainement une erreur de conception quelques part pour que je sois obligé de rajouter cette fonction.
    Tu ne peux pas plutot virtualiser le destructeur?
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  17. #17
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Je suis peut-être passé à côté d’un truc, mais de ce que j’en ai compris :
    - on s’enregistre auprès d’un gestionnaire
    - le gestionnaire rappelle les callbacks quand certains évènements surviennent
    C'est à peu près ça.
    Le gestionnaire n'intercepte pas les événements. On appelle une méthode publique du gestionnaire qui va appeller le callback.

    La liste des événements, autrement dit des callbacks, est aussi vaste que la liste des clés d'une map. Elle est infinie en un sens.
    Quand j’appelle un callback associé à un événement, je lui donne le nom de cet événement. Il ne reste plus qu'une fonction parmi une hypothétique infinité dont on doit tester la signature pour savoir si on peut faire l'appel ou jeter une exception.

    Dans l’idéal, je trouve la première solution meilleure, avec une solution à base de templates pour garder une certaine extensibilité à la compilation.
    Non, car dans mon cas c'est l'utilisateur qui choisi ce qu'il veut écouter.
    D'un autre côté, ma routine choisi elle-même les événements pour lesquels elle appellera un callback. Il ne se produira quelque chose que si l'utilisateur a choisi d'écouter et la routine d'appeler.

    Note : ça ressemble beaucoup à un gestionnaire de signaux/slots. Pourquoi ne pas en utiliser un tout fait (boost ou Qt par exemple) ?
    Je ne sais pas si c'est aussi flexible.

    Oui. Ou dans un fichier .tpp à côté que tu inclus à la fin de ton .h
    Ca ne fonctionne pas.
    Si j'inclus le .tpp dans le .h, ne pouvant pas compiler le .h depuis VS2012, il me dit dans le .tpp que la classe n'est pas définie (puisque je n'inclus plus le .h dans le .tpp).

    [edit]
    Ok, je suis allé un peu vite. Je ne pourrai pas le compiler séparément, mais globalement le compilateur va l'accepter, on est d'accord.
    [/edit]

    C’est ce que te suggérais Loïc avec boost::bind. Sinon, si tu es en C++11, tu peux utiliser des lambdas avec capture du this :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    [this](int arg1) { return this->x + arg1; }
    Attention : cela n’étend pas la durée de vie de ton this : tu es assuré d’un beau segfault si l’objet est détruit mais que la callback est appelée.
    Merci pour ce code. Je ne le comprends pas très bien.
    Où dois-je le mettre? Cela doit-il correspondre à ma fonction que je pousse ensuite dans mon gestionnaire?

    Ok pour la dernière remarque sur la durée de vie. Il va falloir que j'y fasse attention.

    Bonne soirée.

  18. #18
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par Bousk Voir le message
    Je ne pense pas qu'il s'agisse de norme ou spécificité VS. Les templates sont compilés uniquement pour les types utilisés réellement. Donc ton code template n'est pas compilé s'il n'est pas utilisé.
    Heureusement: à chaque déclaration template il faudrait qu'il compile pour tous les types existants ? Et si t'as prévu ton template pour marcher avec des nombres il devrait t'indiquer des erreurs parce que %= n'est pas défini pour ta class Character ?
    Je comprend bien que la fonction ne sera compilée que pour les type utilisés, mais gcc (contrairement à VS) analyse la syntaxe de la fonction même si non utilisée, par exemple ici il y a un problème évident de syntaxe mais ça gène pas VS tant qu'elle est pas utilisée (ce qui parrait logique au final).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template <class T>
    void foo() {
    	cette fonction compile sous VS ni non utilisée,
    	mais pas sous gcc
    }
     
    int main() {
    	return 0;
    }
    C'est sur ce point que je voulais avoir un avis.

    Citation Envoyé par white_tentacle Voir le message
    (sauf s’ils sont portés par l’évènement, auquel cas effectivement tu forwardes l’équivalent d’un eventdata qui peut être n’importe quoi)
    Si l'évènement ne fournis pas d'argument, ça revient à appeler des fonctions de type void callback();, et la callback se débrouille pour choper les valeurs dont elle à besoin.


    Citation Envoyé par leternel Voir le message
    Tu ne peux pas plutot virtualiser le destructeur?
    Bien vu.

  19. #19
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par fanfouer Voir le message
    C'est à peu près ça.
    Le gestionnaire n'intercepte pas les événements. On appelle une méthode publique du gestionnaire qui va appeller le callback.
    Ok, je vois l’idée. Je vais pointer quelques détails qui me paraissent obscurs, et qui doivent être éclaircis avant toute tentative d’implémentation (peut-être qu’ils sont clairs pour toi, mais s’ils ne le sont pas, tu vas dans le mur car tu vas chercher à résoudre techniquement des problèmes fonctionnels, ce qui n’a que peu de chances de fonctionner).

    La liste des événements, autrement dit des callbacks, est aussi vaste que la liste des clés d'une map. Elle est infinie en un sens.
    Donc non-connue à la compilation. Vraiment non-connue à la compilation ou plutôt, dépendante du programme qui utilise le gestionnaire ? Ne fais pas au run-time ce que tu peux faire à la compilation, c’est source d’erreurs.

    Quand j’appelle un callback associé à un événement, je lui donne le nom de cet événement. Il ne reste plus qu'une fonction parmi une hypothétique infinité dont on doit tester la signature pour savoir si on peut faire l'appel ou jeter une exception.
    Tester par rapport à quoi ?

    Ton gestionnaire a connaissance :
    - de tous les types (noms) d’évènements --> qui lui donne cette connaissance ?
    - de la signature de la callback associé à un type d’évènement --> qui lui donne cette connaissance ? Comment comptes-tu la stocker ?
    - de toutes les callbacks enregistrées pour tous les évènements
    - plusieurs callbacks peuvent-elles être enregistrées pour le même évènement ?
    - la vérification de signature callback / type d’évènement ne pourrait-elle pas être faite à l’enregistrement plutôt qu’à l’appel ?

    Non, car dans mon cas c'est l'utilisateur qui choisi ce qu'il veut écouter.
    D'un autre côté, ma routine choisi elle-même les événements pour lesquels elle appellera un callback. Il ne se produira quelque chose que si l'utilisateur a choisi d'écouter et la routine d'appeler.
    L’utilisateur choisit comment ? par rapport à quoi ? La liste est vraiment infinie ?

    (à propos de boost::signal ou Qt)
    Je ne sais pas si c'est aussi flexible.
    À ma connaissance, ça ne permet pas de déclarer des nouveaux signaux durant l’exécution. Si ton besoin passe forcément par là (mais je trouve ça louche comme besoin ), alors ça ne convient pas.


    Je ne pourrai pas le compiler séparément, mais globalement le compilateur va l'accepter, on est d'accord.
    Tu ne peux pas générer un fichier objet avec un template. Donc n’essaie pas de le compiler .

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    [this](int arg1) { return this->x + arg1; }
    Merci pour ce code. Je ne le comprends pas très bien.
    Où dois-je le mettre? Cela doit-il correspondre à ma fonction que je pousse ensuite dans mon gestionnaire?
    C’est l’équivalent de :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    function(arg1) { return this.x + arg1; }
    Ça déclare un objet fonction, qui « binde » le paramètre this (c’est à dire que le this qui est utilisé dans le corps de la fonction est celui du moment où elle a été instanciée), qui prend un paramètre de type entier et renvoie un entier.

    Si tu veux plus de détails, recherche « fonctions lambdas c++», il doit même y avoir de très bons articles là-dessus sur dvp .

  20. #20
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Bonsoir

    Je vois qu'on aime les topics à rebondissement ici

    Je vais essayer d'expliquer simplement mon idée, si il y a des choses qui restent dans le noir, n'hésites pas.

    Citation Envoyé par white_tentacle Voir le message
    Donc non-connue à la compilation. Vraiment non-connue à la compilation ou plutôt, dépendante du programme qui utilise le gestionnaire ? Ne fais pas au run-time ce que tu peux faire à la compilation, c’est source d’erreurs.
    Ca me parait sain comme démarche.

    On va dire que c'est dépendant du programme qui va utiliser ce gestionnaire.
    Le nom des callbacks sera codé en dur.
    Si on prend l'exemple d'une requête HTTP standard, je pourrai avoir l'ago suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    Si code_retour = 200, Alors :
       Si callback_existe ("success"), Alors :
           Appeler_callback ("success", reponse_texte)
       FinSi
    Sinon :
       Si callback_existe ("error"), Alors :
           Appeler_callback ("error", status, error)
       FinSi
    FinSi
    C'est très schématique. Dans la documentation de mon requêteur HTTP on pourra indiquer que la routine fait tel appel avec les arguments qui vont bien.
    L'utilisateur peut alors placer (utiliser les noms "success" et "error" conformément à mon exemple) les callbacks qu'il souhaite dans le gestionnaire.

    Le principe vient surtout de langages sans typage fort ni compilation.
    Vouloir le transposer en C++ est peut-etre une grosse connerie vue qu'il existe d'autres approches du problème tirant partie de la compilation.
    C'est un moyen que j'ai trouvé pour fournir d'un coup un (grand parfois) jeu de callbacks à des routines sans avoir une liste d'arguments longue comme le bras.

    Tester par rapport à quoi ?
    Comme dans le code d'Iradrille, on fait un find dans la map qui contient les callbacks puis le dynamic_cast permet de valider la signature.

    - de tous les types (noms) d’évènements --> qui lui donne cette connaissance ?
    Il connait la liste de tous les callbacks déclarés en retrouvant toutes les clés de la map qui les contient.

    - de la signature de la callback associé à un type d’évènement --> qui lui donne cette connaissance ? Comment comptes-tu la stocker ?
    Il ne connait pas la signature mais le dynamic_cast permet de voir si les arguments fournis conviennent, si j'ai bien tout compris.

    - de toutes les callbacks enregistrées pour tous les évènements
    - plusieurs callbacks peuvent-elles être enregistrées pour le même évènement ?
    Non. Les clés servant d'élément différenciant, il ne peut n'y avoir qu'un seul callback pour tout identifiant.

    - la vérification de signature callback / type d’évènement ne pourrait-elle pas être faite à l’enregistrement plutôt qu’à l’appel ?
    Je dirais que non puisque ça impliquerai d’implanter des contraintes de la routine directement dans le programme l'utilisant. Je préfère avoir une exception au run-time plutôt que d'avoir à étaler ces contraintes de partout.

    L’utilisateur choisit comment ? par rapport à quoi ? La liste est vraiment infinie ?
    L'utilisateur peut mettre autant de callbacks qu'il souhaite. Rien ne dit qu'ils seront utilisés au run-time.

    J'espère avoir été clair.
    Si les gestionnaire de signaux permettent de faire la même chose, pourquoi pas.

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 3 123 DernièreDernière

Discussions similaires

  1. [VB6] Sauvegarder une collection d'objets
    Par Sayagh dans le forum VB 6 et antérieur
    Réponses: 7
    Dernier message: 19/09/2003, 11h58
  2. [VB6] la collection controls
    Par tomnie dans le forum VB 6 et antérieur
    Réponses: 3
    Dernier message: 30/04/2003, 17h03
  3. Comment créér une collection sous Delphi
    Par PsyKroPack dans le forum Langage
    Réponses: 6
    Dernier message: 11/02/2003, 13h20
  4. [VB6] Modifier la clé d'un élément d'une collection
    Par Ricou13 dans le forum VB 6 et antérieur
    Réponses: 3
    Dernier message: 21/11/2002, 14h49

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo