Instancier des objets dans une fonction et autres problèmes

Bonjour à tous, j’espère que le confinement se passe bien pour vous. :slight_smile: J’aurais besoin d’aide à propos d’un de problèmes logiques que je rencontre entre les classes d’objets et les fonctions.
Pour me présenter vite fait , je suis un amateur aguerris depuis 4 ans sur arduino. J’ai tout appris en me débrouillant par moi-même, expérimentant, apprenant par des tutos et forum. J’ai récemment plonger dans l’univers de la POO. C’est génial mais là je coince depuis plusieurs jours… :confused: Votre aide me fera le plus grand bien :slight_smile:

Je serais court et efficace:

*Ps:J’ai volontairement simplifier mon code pour que vous voyez le coeur de mon problème. Je précise que c’est pour faire apparaitre des boutons sur un écran TFT 3,5". *

Problème N°1:
Comment instancier un objet dans une fonction et le réutiliser en dehors de la fonction ?

#include <ButtonPerso.h>         // Class que j'ai fais moi

void fonctionInstancedObjet(){   // Création de la fonction
ButtonPerso Bouton1 ;}           // Ou j'instancie un objet

void setup() {
  
fonctionInstancedObjet();       // Je fais appel à la fonction qui instancie donc l'objet
Bouton1.display();              // Je veut me servir de l'objet instancié, mais ça me dit:
                                   error: 'Bouton1' was not declared in this scope

}

Si j’appelles des méthodes de Bouton1 dans la fonction, là oui ça marche. Mais comment en dehors ? Par pointeurs j’imagine…

Problème N°2:
Comment instancier des objets dans une boucle ?
J’ai trouvé cette technique qui me permet de choisir le nombre d’objet que je crées et ainsi les paramétrer d’une différente manière chacun.

byte nbdeBOUTON = 10;                      // Je veux 10 bouton
ButtonPerso PleinDeBouton[nbdeBOUTON] ;    // J'instancie automatique un tableau de 10 boutons

for (byte i= 0 ; i < nbdeBOUTON ; i ++){   // Boucle for avec l'appel d'une methode pour tout les
                                              bouton[1] 2,3,4 etc...
PleinDeBouton[i].display();
}

Mais j’aimerais pouvoir nommer mon tableaux lui-même dans une boucle du genre:

byte nbdeBOUTON = 10;                       // Je veux 10 bouton

for (byte i= 0 ; i < nbdeBOUTON ; i ++){    // Je crée une boucle qui vas instancier 10 objets ButtonPerso nommé différemment.
String Nom = "Bouton"+'i' ;                 // Je sais c'est pas comme ça qu'il faut faire mais c'est l'idée. :D
ButtonPerso Nom ;                           // J'instancie l'objet Nom qui est égal à Bouton1, Bouton2 etc...
Nom.display();
}

Bouton5.faisCeci()                         // L'idée serais que je puisse me servir de mes boutons comme ça aussi :D

L’idée de tout ça est de créer des fonctions qui mettront des boutons rectangulaires en colonne. J’y arrive déjà très bien un à un, avec des fonctions qui check si le stylet appuie sur le bouton. Je peux faire appaitre des colonnes de rectangle, mais je ne peux par utiliser les objets instanciés pour faire des fonctions qui check tout les boutons rectangulaires sur l’écran. En somme des objectifs que tous créateur de GUI rencontre… hihihi.

Voilà mes 2 plus gros problèmes :slight_smile: Je vous remercie 32767 fois par avance. M’excuse aussi si fautes orthographes. Et espère que votre aide servira aussi à d’autres qui passeront par là :).

deux truc à lire:

Si j'appelles des méthodes de Bouton1 dans la fonction, là oui ça marche. Mais comment en dehors ? Par pointeurs j'imagine....

1/ la notion de portée des variables (scope). Si vous déclarez une variable dans une fonction elle est locale à cette fonction et donc son nom est inconnu ailleurs. (votre souci avec Bouton1). Il faudrait effectivement retourner le pointeur sur l'instance si vous voulez l'utiliser ailleurs

Problème N°2:
Comment instancier des objets dans une boucle ?

2/ instanciation dynamique --> on peut créer une instance en appelant new sur la classe. On reçoit en retour un pointeur vers l'instance créée;

Si vous avez un classe Toto vous pouvez déclarer une tableau global de pointeurs vers des instances de Toto

Toto* mesInstances[10];

et ensuite quand vous voulez créez vraiment l'instance faire un

 mesInstances[3] = new Toto; // ou new Toto(10,20, "coucou"); si le constructeur a des paramètres

en C++ le nom d'une variable ne veut plus rien dire une fois compilé, il est dégagé - il ne reste pour le compilateur que des adresses en mémoire. donc vous ne pouvez pas fabriquer un nom de variable sous forme de String.

Sur la question 1 : tu peux déclarer une instance d'un objet et le passer en argument à une fonction. Ca se fait ici par exemple : dans ce code, une instance d'un file système est créé dans le setup et passé en argument des fonctions appelées.
Appel :

    listDir(SPIFFS, "/", 0);

Fonction :

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){

Utilisation dans la fonction :

    File root = fs.open(dirname);

Question 2 : je ne sais pas mais je n'ai pas l'impression qu'on puisse faire ça comme ça.

Je répond sur le problème N°1

Un objet, tout comme une variable, a une portée et n’est utilisable que dans celle ci. La portée débute par sa définition, et se termine à la fin du bloc qui l’a défini.

  • si ton objet est global, il est utilisable partout
  • si ton objet est crée dans un fonction, il ne peut être utilisé QUE dans cette fonction.

Dans le code, est déclaré un objet statique. Il est donc défini et sa place est réservé que dans la fonction. C’est pour cela qu’en dehors il n’existe pas “Bouton1’ was not declared”

Si on veut pouvoir l’utiliser partout (dans deux fonctions différentes, il faut qu’il soit global, c’est à dire défini en dehors de la fonction. On peut par contre utiliser une première fonction fonctionInstancedObjet qui lui donne son contenu et l’utiliser après dans le setup. On fait alors:

#include <ButtonPerso.h>         // Class que j'ai fais moi

ButtonPerso Bouton1 ;            // Ou j'instancie un objet


void fonctionInstancedObjet(){   // Création de la fonction
Bouton1.largeur =....
Bouton1.couleur =....
}
void setup() {
 
fonctionInstancedObjet();       // Je fais appel à la fonction qui instancie donc l'objet
Bouton1.display();              // Je veut me servir de l'objet instancié, mais ça me dit:
                                   error: 'Bouton1' was not declared in this scope

}

Mais bien souvent, e déclarant le bouton, on lui donne ses paramètres:

ButtonPerso Bouton1(largeur, hauteur, couleur...) ;            // Ou j'instancie un objet

On peut aussi utiliser des boutons dynamiques.

On peut lors de la définition réserver la place et lui fournir ses attributs (cela peut se faire en dehors des fonctions, avant le setup par exemple)

On peut aussi définir simplement le nom, pareillement en dehors des fonctions, mais uniquement le nom. C’est un pointeur qui ne pointe sur rien.
Puis dans la fonction qui crée le bouton, on va réserver l’espace et lui donner ses attributs. Comme l’espace associé est lié à un objet qui n’est pas défini dans la fonction (l’objet est défini avant), la place n’est pas libérée à la fin de la première fonction et est donc utilisable après.

A ma page PushZoneExemple on peut trouver des exemples d’objets boutons définis des trois façons.

Une autre solution possible est de créer une liste d’objets qui peut être dynamique (liste vide au départ, et si on veut un bouton supplémentaire, on allonge la liste de pointeurs), dans la quelle les pointeurs visent les boutons. On peut alors faire une fonction qui crée ce bouton en demandant de la place.

Problème N°2:

Sur ma page RadioCircleExemple on peut trouver un exemple qui crée 40 boutons. Si cela peut t'inspirer....

RÉPONSE 1 : J-M-L
Déjà merci de ta réponse :slight_smile: .
J’ai appliqué ce que tu me conseille pour le probleme N°2 en faisant comme ça:

Par contre pour utilisé des méthodes avec mes objets instancier, j’utilise → mais ça je sais faire :wink:

void fonctionInstanceObjet(byte nbdeBOUTON){        // Une fonction qui crée le nombre d'objet que je veux

ButtonPerso* PleinDeBouton[nbdeBOUTON] ;             // Je crée ton tableaux de pointeurs d'objets

for (byte i= 0 ; i < nbdeBOUTON ; i ++){
PleinDeBouton[i] = new ButtonPerso ;                  // Et la je créer vraiment l'instance
PleinDeBouton[i]->parametersOneButton(12,42,120,32); // Ici j'y passe des paramètres par une méthode 
PleinDeBouton[i]->display();                         // Encore une méthode, yes tous marche :)
}

Par contre, j’ai besoin d’une petite précision vis à vis de ta réponse de mon problème N°1 pour que je puisse l’utilisé en dehors de la fonction.

void setup() {

fonctionInstanceObjet(4);          // Mes objets sont créés

PleinDeBouton[1]->faisCeci();       // Mais comment retourner le pointeur de mon instance d'objet ????
                                      error: 'PleinDeBouton' was not declared in this scope.
}

RÉPONSE 2 : LESEPT
Déjà merci de ta réponse :slight_smile: .

Mais j’ai déjà appris à passer des objet en argument de fonction. Du coup là l’objet est crée avant et est “rentrée” dans la fonction. Moi je veux les crées dans un fonction et faire “sortir” l’objet. :’( Peut-etre je demande l’impossible. :sob: :sob:

RÉPONSE 23: VILROI

Déjà merci de ta réponse :slight_smile: .

Mais alors du coup avec ta réponse, je tombe des nu … Car j’y avais un peu déjà penssé, mais il me semblais que c’était un peu… “disons sale” et qu’il y avais d’autre moyens de s’y prendre. (c’est pour ça qu’il faut que je revois la manière dont j’ai construit mon programme, ma class etc… ^^)

J’avais déjà bien remarqué la logique de variable local et global et tout. Mais je voulais automatisé la création du nombre exacte d’objets que j’ai besoins.
Mais sI j’ai bien compris, je serais obligé de crée une longue liste d’objets avant , de manière global, et de les utilisés au fur à mesure ensuite de manière plus local.

Je suis aller voir vite fait ton site, très impressionnant ! :smiley:
Yes je comprend aussi ta deuxieme solution, celle que t’utilise pour les 40 Boutons, le problème c’est pour checker si quand on appuie dessus et tout. Je vois que tu confie ça à : scanEvent();

Peut-être faut-t’il que je check de la même manière ^^.

EN TOUT CAS MERCI POUR TOUUUUUUT ! :slight_smile:

void fonctionInstanceObjet(byte nbdeBOUTON){        // Une fonction qui crée le nombre d'objet que je veux

ButtonPerso* PleinDeBouton[nbdeBOUTON] ;             // Je crée ton tableaux de pointeurs d'objets

for ......
}

BoutonsPerso étant défini dans la fonction fonctionInstanceObjet, ne sera pas utilisable en dehors. Par contre

ButtonPerso* PleinDeBouton[nbdeBOUTON] ;             // Je crée ton tableaux de pointeurs d'objets

void fonctionInstanceObjet(byte nbdeBOUTON){        // Une fonction qui crée le nombre d'objet que je veux

for ......
}

Permet de récupérer et d'utiliser le pointeur dans le setup

Je reprends le problème N° 2. Dans le post initial:

Bouton5.faisCeci()                         // L'idée serais que je puisse me servir de mes boutons comme ça aussi :D

Comme le dit si bien @J-M-L Bouton n’existe plus après la compilation. Mais cela pose un problème: si tu as 10 boutons il faudra 10 lignes, vu que c’est 10 noms différents.
Si tu veux garder le nom, il suffit de mettre un attribut dans le bouton qui contient son nom. Cela existe sur Windows par exemple, je ne le reprend pas car cela utilise beaucoup de RAM.

L’idée de tout ça est de créer des fonctions qui mettront des boutons rectangulaires en colonne. J’y arrive déjà très bien un à un, avec des fonctions qui check si le stylet appuie sur le bouton. Je peux faire appaitre des colonnes de rectangle, mais je ne peux par utiliser les objets instanciés pour faire des fonctions qui check tout les boutons rectangulaires sur l’écran.

Mais si les noms sont différents, on ne peut plus faire une fonction qui les scanne tous. Ou alors les définir comme
Bouton[5].faisCeci()                        // L'idée serais que je puisse me servir de mes boutons comme ça aussi :D, mais on revient au problème précédent.


Je vois que tu confie ça à : scanEvent();

ScanEvent regarde le touchpad, et cherche un bouton qui peut être sélectionné. On peut parfaitement s’en passer, et demander à chaque bouton si il est intéressé. En fait la question qui doit se poser est “que se passe-t-il lorsque l’on appuie sur l’écran?”.
Il existe des écrans qui génèrent une interruption lorsque l’on touche l’écran, d’autres non. Je suis dans ce dernier cas, et comme je n’ai pas d’inter généré, je scanne si je n’ai rien d’autre à faire.

Avec mon écran à 10 balles, scanEvent scrute le touchpad et cherche alors un responsable. On peu aussi, au lieu d’appeler un gestionnaire, appeler une fonction particulière de chaque bouton, qui va alors faire le travail: regarder le touchpad et au cas où agir. Mais dans ce cas le touchpad serait appelé une fois par bouton.

Quand on écrit quelque chose, une bonne idée consiste à voir ce qui existe déjà et de s’en inspirer, prendre ce qu’il y a de bon et changer ce qui ne nous convient pas. Par exemple quasiment tous les constructeurs automobile ne vont pas se poser la question combien de roues va-t-on mettre, ils en mettent 4 d’office.
Il existe aussi quelqu’un qui a fait un travail proche du mien (pour les boutons et les timers), je l’ai vu assez tard, mais j’étais parti quasiment dans la même direction. Cette notion de gestionnaire d’évènements est celle que je connaissais de Windows. Un programme Windows ne fait rien sauf quand arrive un évènement.

Peut-être faut-t’il que je check de la même manière ^^.

Je ne vois pas trop comment faire autrement, sinon faire un loop qui ressemble à
void loop() {
bouton1.run();
bouton2.run();
bouton3.run();
bouton4.run();
for (uint8_t i=0; i<10; i++) boutons.run();
}
Cela fonctionne parfaitement avec un code fixe, mais pas pour une bibliothèque qui ne sait pas à l’avance le nom et le nombre de boutons.

Si ton programme est unique, cette solution est bien plus simple. Dans mon cas, pour une bibliothèque je me complique la vie une bonne fois pour toute, et après c’est plus simple. Mais cela utilise plus de code.

debutang:
RÉPONSE 1 : J-M-L
Déjà merci de ta réponse :slight_smile: .
...
Par contre, j'ai besoin d'une petite précision vis à vis de ta réponse de mon problème N°1 pour que je puisse l'utiliser en dehors de la fonction.

Le fait que vous ne puissiez pas utiliser le nom en dehors de la fonction c'est la notion de scope (nom de variable inconnue), mais il y a pire c'est que même si le pointeur était partagé, l'instance aurait disparue.

Tenez jetez un coup d'oeil à ce code

class Demo
{
  public:

    int valeur;

    Demo(int v) : valeur(v) {
      Serial.print(F("instance créée:")); Serial.println(valeur);
    }

    ~Demo() {
      Serial.print(F("instance détruite:")); Serial.println(valeur);
    }

    void imprimeValeur()
    {
      Serial.println(valeur);
    }
};

Demo* creerInstanceLocale(int v)
{
  Demo uneInstanceLocale(v);
  return & uneInstanceLocale;
}

Demo* creerInstancePersistante(int v)
{
  return new Demo(v);
}

void setup() {
  Demo* instancePtr;

  Serial.begin(115200);
  instancePtr = creerInstanceLocale(10);
  Serial.print(F("Valeur instance locale:"));  instancePtr->imprimeValeur(); // imprime n'importe quoi, l'instance a été détruite

  instancePtr = creerInstancePersistante(20);
  Serial.print(F("Valeur instance persistante:"));  instancePtr->imprimeValeur(); // on a bien 20, l'instance existe de manière permanente
}

void loop() {}

le moniteur série (à 115200 bauds) affichera

[color=purple]
instance créée:10
[color=red]instance détruite:10
[/color]Valeur instance locale:[color=red]101[/color]
[b][color=teal]instance créée:20
Valeur instance persistante:20[/color][/b]
[/color]

=> Quand vous instanciez de manière statique un objet dans une fonction, cette variable est locale en portée (connue que dans cette fonction) ainsi que sa durée de vie (c'est pour cela que vous avec la confirmation que l'instance est détruite - le destructeur est appelée - lors de la sortie de la fonction). Bien que je retourne le pointeur sur l'objet crée, il ne pointe sur plus rien et l'appel de fonction donne n'importe quoi (affichage de 101 au lieu de 10). Le compilateur va d'ailleurs se plaindre en disant

[color=orange]warning: address of local variable 'uneInstanceLocale' returned [-Wreturn-local-addr]
   Demo uneInstanceLocale(v);
        ^~~~~~~~~~~~~~~~~[/color]

Par contre la fonction creerInstancePersistante() alloue l'instance de manière dynamique avec new. La mémoire est allouée et comme je retourne son pointeur je peux l'utiliser ailleurs maintenant et demander la valeur (20) qui s'affiche correctement.

PS/ Si vous faites un new et ne conservez pas le pointeur obtenu quelque part, vous avez une fuite mémoire car l'instance est allouée mais le code ne peut pas l'utiliser

vileroi:
Cela fonctionne parfaitement avec un code fixe, mais pas pour une bibliothèque qui ne sait pas à l'avance le nom et le nombre de boutons.

Sans doute pas du niveau de cette discussion, mais si on sait au total combien d'instances on va devoir utiliser, avec les template on pourrait paramétrer la classe dans la bibliothèque pour qu'elle même se souvienne de toutes ses instances et ensuite donc on pourrait appeler une méthode de classe Bouton.run();qui se chargerait d'appeler run sur toutes ses instances