Je veux passer à un objet un pointeur générique sur une fonction, et une liste de paramètres pour cette fonction, de sorte à ce qu'il puisse appeler n'importe quelle fonction. Je crois avoir déjà vus que ça se faisait dans je ne sais plus quelle bibliothèque, mais je ne sais pas trop comment m'y prendre. Quelle est la meilleur méthode pour écrire un tel objet?
A priori, tu as quand même loupé quelques notions car, a priori, si on peut effectivement expliquer au compilateur que l'on veut représenter une fonction dont on ignore le nombre (et, pourquoi pas le type) des paramètres dont elle a besoin -- regarde du coté de std::function pour cela -- il n'en demeure pas moins que la fonction transmise devra quand même bien, à un moment donné, pouvoir être appelée.
Or, une fonction ne pourra être appelée que ... si on lui transmet le nombre adéquat de paramètre dont les types correspondent aux type des paramètre qu'elle s'attend à recevoir .
Pour que "la magie opère", il faudra donc bien, à un moment donné, décider du nombre de paramètre et de leur type que la fonction destinée à être appelée devra accepter.
Il est vrai que les possibilité sont nombreuses, car on peut très bien décider de transmettre une des fonctions parmis
int add(int i, int j){
std::cout<<"add called with values "<<i<<" and "<<j<<"\n";
return i+j;
}
int substract(int i, int j){
std::cout<<"substract called with values "<<i<<" and "<<j<<"\n";
return i-j;
}
int multiply(int i, int j){
std::cout<<"multiply called with values "<<i<<" and "<<j<<"\n";
return i*j;
}
int divide(int i, int j){
assert(j!=0 && "nul dividend detected");
std::cout<<"divide called with values "<<i<<" and "<<j<<"\n";
return i/j;
}
à la fonction
int execute(std::function<int(int,int)> fun, int i, int j){
fun(i,j);
}
sous une forme proche de
int main(){
auto addResult = execute(&add, 3,4);
auto substractResult = execute(&substract, 10,3);
auto multiplyResult = execute(&mulitply, 15,2);
auto divideResult = execute(÷, 55,5);
}
Mais on peut aussi expliquer au compilateur que l'on ne sait pas encore trop bien quel type de donnée sera utilisé, même si on sait comment les données sont utilisées sous une forme proche de
template <typename T>
T add(T i, T j){
std::cout<<"add called with values "<< i<<" and "<<j<<"\n";
return i+j;
}
template <typename T>
T substract(T i, T j){
std::cout<<"substract called with values "<<i<<" and "<<j<<"\n";
return i-j;
}
template <typename T>
T multiply(T i, T j){
std::cout<<"multiply called with values "<<i<<" and "<<j<<"\n";
return i*j;
}
template <typename T>
T divide(T i, T j){
assert(j!=0 && "nul dividend detected");
std::cout<<"divide called with values "<<i<<" and "<<j<<"\n";
return i/j;
}
template <typename T>
T execute(std::function<T(T,T)> fun, T i, T j){
return fun(i,j);
}
int main(){
auto addResult = execute(add<int>, 3,4); // utilise le type int
auto substractResult = execute(substract<float>, 10.15f,3.1415f); // utilise le type float
auto multiplyResult = execute(multiply<unsigned>, 15u,2u); // utilise le type unsigned int
auto divideResult = execute(divide<double>, 55.15d,5.03d); // utilise le type double
}
Et on peut même envisager une certaine adaptation du prototype de la fonction qui devra être appelée aux informations dont dispose la fonction appelante au travers des expressions lambda et de std::bind.
Si bien que l'on dispose d'un panel de possibilité quasi illimité, une fois que l'on sait ce que l'on veut faire ;) Mais il n'empêche que la première question à laquelle tu devras toujours apportere une réponse, pour pouvoir faire quelque chose de correct dans ce genre de situation est et restera
Quel est le prototype des fonctions que je veux transmettre afin de pouvoir les appeler?
BelhouariSéhane a écrit:
Quelle est la meilleur méthode pour écrire un tel objet?
J'ai l'impression, à la manière dont tu exprimes ta demande, que tu souffre du "syndrome java", dans le sens où tu sembles considérer la programmation orientée objets comme la véritable panacée, comme un outil qui permet tout aussi bien d'enfoncer un clou dans un mur que de faire le café.
Je te rassure tout de suite, ce syndrome est très courant chez les débutants en C++, et ce n'est donc pas un reproche: c'est une constatation
Cependant, je me sens obliger de te contredire, car la programmation orientée objet n'est jamais qu'un outil qui est -- comme tous les outils -- admirablement adapté lorsqu'il est utilisé correctement et dans les bonnes conditions et totalement inadapté lorsqu'il est utilisé dans "de mauvaises conditions".
Si je parle du "syndrome java", c'est parce que l'on n'a pas le choix du paradigme dans ce langage: nous devons utiliser le paradigme orienté objet, à l'exclusion de tout autre.
Tu ne tardera pas à te rendre compte que C++offre beaucoup plus de liberté à ce niveau là, car il permet d'utiliser et de mélanger trois paradigmes, à savoir:
le paradigme "purement procédural" (les fonctions libres)
le paradigme orienté objet et
le paradigme générique (les fonctions template)
C++ nous permet donc de n'avoir recours à l'orienté objet que ... si l'on a un réel avantage à l'utiliser. Or, il y a énormément de situations dans lesquelles une fonction libre, template ou non, apportera une bien meilleure soluton à un problème donné que ne pourrait le faire l'alternative orientée objet (dés que l'orienté objet nous inciterait à créer des fonctions statiques, par exemple).
Le comité de normalisation en d'ailleurs arrivé à un point tel qu'il commence à rendre un grand nombre de fonctions membres de classes -- créées selon l'approche orientée objet -- accessibles sous forme de fonctions libres à l'intérieur même de la bibliothèque standard (voir, pour l'exemple, std::begin, std::end ou encore std::size).
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Merci pour la réponse qui m'as beaucoup instruit, mais je crois que j'ai mal été compris.
A vrai dire, j'utilise direct 2D, et je veux programmer un bouton qui appelle une fonction lorsqu'il est cliqué (je l'ai gardé secret pour ne pas me faire taper sur les doigts, personne n'aime directX sur ce forum).
En faite, je veux que le bouton marche de manière générale, donc que je puisse après avoir inclus un header "Bouton.h" créer un bouton et lui passer n'importe quelle fonction et liste de paramètres pour qu'il puisse l'appeler lorsqu'il est cliqué. Ainsi, je veux faire une méthode:
C'est là que je bloque. Il faut que n'importe quelle fonction retournant void puisse être entrée dans le bouton quelque soit ses paramètres. Je ne vois pas comment faire ça avec des std::function et des Template.
A vrai dire, j'utilise direct 2D, et je veux programmer un bouton qui appelle une fonction lorsqu'il est cliqué (je l'ai gardé secret pour ne pas me faire taper sur les doigts, personne n'aime directX sur ce forum).
Pourtant c'est bien DirectX. Par contre, Direct2D, c'est dans les vieilles version de DirectX non ?
Si j'étais toi, je ferais une classe abstraite :
class ClicSurBouton()
{
public:
virtual Fonction()=0;
}
Et chaque action spécifique que tu fais hérite de ClicSurBouton, ainsi de manière générique tu peux appeler ClicSurBouton, et la fonction spécifique que tu donnes, en réalité, c'est une classe qui hérite que tu crées pour l'occasion.
En faite, je veux que le bouton marche de manière générale, donc que je puisse après avoir inclus un header "Bouton.h" créer un bouton et lui passer n'importe quelle fonction et liste de paramètres pour qu'il puisse l'appeler lorsqu'il est cliqué. Ainsi, je veux faire une méthode:
Attention, il faut respecter le SRP, et là, tu es bien parti pour ne pas le faire!!!
Le SRP (Single Responsability Principle ou principe de la responsabilité unique) nous dit que chaque variable, chaque classe, chaque fonction ne doit s'occuper que d'une seule chose pour pouvoir s'en occuper correctement.
Le rôle d'un bouton, par exemple, c'est d'indiquer lorsque l'utilisateur clique dessus. La réaction de l'application face à ce clique n'est pas du ressors du bouton, mais "d'autre chose"; de manière à pouvoir mettre n'importe quelle logique (y compris les plus complexes) en oeuvre.
L'idéal pour arriver à ce résultat, c'est de mettre en place un système de signaux et de slots. Pour faire simple, et te raccrocher à quelque chose que tu connais peut-être, nous pourrions presque dire que c'est de mettre le patron de conception "observateur" en oeuvre.
Nous pourrions presque comparer ce système à un système de station radio et d'auditeur: la station radio (le bouton) va fonctionner même lorsque personne ne l'écoute: ce n'est pas parce que l'auditeur éteint sa radio que la station radio cesse d'émettre, nous sommes bien d'accord?.
Dans l'autre sens, si un auditeur est intéressé par ce qui se dit sur une station radio particulière, il devra aller se connecter sur la fréquence correspondant à la station qui l'intéresse pour pouvoir l'écouter.
Cette manière de faire sera beaucoup plus souple, parce que grâce à elle, tu pourras réduire la notion de bouton "à sa plus simple expression". Je m'explique:
Dans cette configuration, un bouton se réduit à "une surface donnée, qui affiche (ou non) un texte ou une image capable d'émettre un signal lorsque l'utilisateur clique sur la souris et que la souris "pointe" sur la surface donnée.
Autrement dit, tous les boutons sont identiques, en dehors de quelques caractéristiques spécifiques, telles que:
le texte affiché
la superficie qu'il recouvre
l'image affichée ou
la position de son coin supérieur gauche.
Avec l'énorme avantage qu'une seule sorte de bouton pourra servir à "n'importe quel usage", vue que les données qui permettront de faire appel à la fonction qui doit être exécutées se trouveront "ailleurs" (au niveau de l'élément qui doit réagir au clique), et que toute la logique pourra dés lors être "mise en branle" par l'appel d'une simple fonction ne prenant aucun paramètre disponbile sur l'élément devant réagir
Tel que tu es parti, tu en arriverais à une situation telle que tous les boutons seraient forcément différents, car ils devraient tous, en plus, disposer des informations nécessaires pour permettre ..; d'appeler la fonction qui doit être exécutée.
C'est à dire que, si tu veux qu'un bouton puisse appeler une fonciton qui ne prend qu'un entier comme paramètre, ce bouton devra disposer d'une donnée sous forme de int. Et, juste après, tu vas vouloir créer un fonction capable d'appeler une fonction qui prend un float et une position comme paramètre, et qui devra donc disposer d'une autre sorte de bouton, disposant d'un float et d'une position pour pouvoir appeler la fonction en question.
Bref, si tu envisage de faire appel à des fonctions qui présentent dix prototypes différents (par rapprot au nombre ou au type des paramètres dont elles ont besoin pour fonctionner), tu vas te retrouver à devoir créer ... dix sortes de boutons différents qui ne pourront jamais servir qu'à ... mettre une logique bien particulière en oeuvre.
Il est possible d'avoir un système de signaux et de slots qui tient sur 200 lignes de codes (plus les commentaires de génération automatique de code) , ainsi que j'en ai la preuve ==>ici<==.
L'idée, c'est que, grâce à ce système, le code d'un bouton pourrait être aussi simple que
class Button{
using sig_t = Tools::Signal<>;
public:
using slot_t = typename sig_t::slot_type
Position coin_sup_gauche;
int hauteur;
int largeur;
std::string text
void draw();
Tools::Connection connect(slot_t slot){
return sig.connect(slot);
}
void onEventClique(ClickEvent * event){
/* ... y aura peut être quelque chose à faire avant */
sig();
}
private:
sig_t sig;
};
Et, à partir de là, tu pourras créer autant "d'auditeurs" différents que tu veux sous une forme prochede
/* un récepteur voulant appeler une fonction qui prend
* un int comme paramètre
*/
class Truc{
public:
using callbackk_t= std::function<void(int)>;
Truc(callback_t const &call):call_{call}{
}
void connect(Button & button){
conn_= button.connect([&](){this->onClique();};
}
void onClique(){
/* tout ce qui pourrait devoir être fait avant */
call_(x);
}
private:
Tools::Connection conn_;
callback_t call_;
int x;
};
/* un autre qui appelle une fonciton prenant deux
* float comme paramètres
*/
class Brol{
public:
using callbackk_t= std::function<void(float, float)>;
Truc(callback_t const &call):call_{call}{
}
void connect(Button & button){
conn_= button.connect([&](){this->onClique();};
}
void onClique(){
/* tout ce qui pourrait devoir être fait avant */
call_(x, y);
}
private:
Tools::Connection conn_;
callback_t call_;
float x;
float y;
};
dont l'utilisation serait proche de
/* la fonciton appelée par Truc */
void trucFunction(int x){
/* ... */
}
/* la fonction appelée par Brol */
void brolFunction(float x, float y){
/*... */
}
int main(){
/* le bouton qui fait réagir Truc */
Button buttonTruc;
/* et notre donnée truc, bien sur */
Truc t(trucFunction);
t.connect(buttonTruc);
/* le bouton qui fait réagir Brol */
Button buttonBrol;
/* et notre donnée Brol, bien sur */
Brol b(brolFunction);
b.connect(buttonBrol);
/* ... tout le reste pour utiliser les deux boutons */
}
Bien sur, je n'ai mis que les choses absolument essentielles à la compréhension. J"espère que tu n'arriverais sans doute pas à faire quelque chose d'aussi simple avec l'approche que tu voulais prendre
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Je dois avouer que la méthode avec des slot me fait peur. Je vais pour l'instant faire un tableau de bool indiquant l'état des boutons, ce qui respecterais le SRP. Après, l'habile méthode à partir d'héritage est aussi assez tentante.
Sinon, question subsidiaire: Si qqn s'y connais en directX, je n'arrive pas à centrer le texte dans le bouton, car je ne trouve pas sa longueur, et je ne trouve pas comment faire dans la doc. Si il y aurait un tutoriel à jours qui traite de ce sujet, je suis prenant.
Merci pour vos réponses.
- Edité par BelhouariSéhane 20 juillet 2019 à 12:48:44
Je dois avouer que la méthode avec des slot me fait peur.
Pourquoi? Un signal n'est jamais qu'une fonction qui est appelée à un moment bien particulier ( lorsque le bouton subit le clique, dans le cas qui nous occupe), dont la logique va parcourir une liste de fonctions afin de les appeler les unes après les autres.
La mise en oeuvre est assez surprenante, parce que le code utilise le paradigme générique, mais l'utilisation en est particulièrement simple : une fois que tu as décidé quels paramètres devront être transmis aux fonctions appelées (aucun, dans le cas qui nous occupe), il n'y a plus qu'à "remplir la liste" des fonctions qui doivent être appelées (au travers de la fonction connect) ;). la connexion, renvoyée par cette fonction permet de déconnecter les fonctions qui, pour une raison ou une autre, ne doivent plus être appelées.
BelhouariSéhane a écrit:
Je vais pour l'instant faire un tableau de bool indiquant l'état des boutons, ce qui respecterais le SRP.
Cela te rendrait la tâche beaucoup plus compliquée, car cela impliquerait:
que tous tes boutons devraient être en mesure d'accéder à ton tableau de booléen (ce qui n'est déjà pas la meilleure des choses à faire) et
que tu devrais parcourir l'ensemble de ce tableau pour pouvoir déterminer quelle fonction doit être appelée parce que le bouton correspondant a subit un clique.
BelhouariSéhane a écrit:
Après, l'habile méthode à partir d'héritage est aussi assez tentante.
Si tu pense à la mise en oeuvre d'un patron de conception observer ou autre, tu as bien tors, car ce n'est pas (et de loin) plus facile à mettre en place
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Si je parle du "syndrome java", c'est parce que l'on n'a pas le choix du paradigme dans ce langage: nous devons utiliser le paradigme orienté objet, à l'exclusion de tout autre.
Tu ne tardera pas à te rendre compte que C++offre beaucoup plus de liberté à ce niveau là, car il permet d'utiliser et de mélanger trois paradigmes, à savoir:
le paradigme "purement procédural" (les fonctions libres)
le paradigme orienté objet et
le paradigme générique (les fonctions template)
Fonctions libres => en java fonctions statiques d'une classe utilitaire (qui sert de namespace)
généricité => depuis java 5 (ce qui n'existe pas - mais faut-il le déplorer ? - c'est les spécialisations)
paradigme fonctionnel => lambdas depuis java 8.
En fait les langages ne sont pas destinés aux même usages, les comparaisons sont assez futiles. Par exemple la STL de C++ est certainement très efficace (en temps d'exécution), mais assez biscornue (donc cout de développement plus élevé), quand on voit les simagrées qu'il faut faire pour enlever certains éléments d'une collection
Bah oui, en C++, remove_if, ça remove pas, ça déplace. Le paradigme des itérateurs, c'est sympa au début, mais bon, est-ce si commode ?
Alors qu'en java
ArrayList<Integer> nombres = new ArrayList<Integer>();
...
// enlever les nombres divisibles par 3
nombres.removeIf(n -> (n % 3 == 0));
Parce qu'au lieu de se baser sur les itérateurs, on a une hiérarchie de conteneurs, donc polymorphisme au niveau des opérations applicables.
- Edité par michelbillaud 22 juillet 2019 à 10:18:51
Parmètres pour fonction à parmètres inconnus
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Recueil de code C et C++ http://fvirtman.free.fr/recueil/index.html
Eug