LOCODUINO

Parlons Arduino => Débuter => Discussion démarrée par: jac.fil le mai 28, 2018, 07:19:52 pm

Titre: Tableau d'objet
Posté par: jac.fil le mai 28, 2018, 07:19:52 pm
Question de débutant en Arduino ;)
Dans la mesure ou La référence du language sur le site Arduino ne fait aucune mention à la P.O.O, est-ce que le language C++ est totalement supporté et compilé par l'IDE?
Ma question plus précisément, peut-on créer des tableaux d'objets et dans ce cas quel est la syntaxe pour les créer ?
C'est peut-être une question triviale pour les spécialistes et je vous avoue que je n'ai pas encore lu tous les articles de la "Bible LOCODUINO", nul n'est parfait  :) :)
Titre: Re : Tableau d'objet
Posté par: Thierry le mai 28, 2018, 08:44:52 pm
Bonjour
La question n'est pas aussi triviale qu'il y parait.
Le mieux est de trouver les tutoriaux sur le C++ sur le net, ils sont légions. Cela dit, le mien est pas mal :) . Le mieux est de commencer par le début :http://www.locoduino.org/spip.php?article85 (http://www.locoduino.org/spip.php?article85).
Enfin pour répondre à la question posée, l'IDE Arduino utilise le compilateur Open Source GCC qui est complet, et donc peut compiler du vrai C++ avec toutes ses subtilités et ses chausse-trappes.
Titre: Re : Tableau d'objet
Posté par: jac.fil le mai 29, 2018, 08:22:16 am
Merci pour la réponse, en effet j'ai relu les articles sur les objets sur LOCODUINO et je vais approfondir le sujet des tableaux car je vois pas comment utiliser les objets "cantons", contenant des objets "détecteurs IR",  "zone d'arrêt " et "led TCO" pour un début.
Merci aussi pour les conseils, je vais potasser le C++
À suivre ....
Titre: Re : Tableau d'objet
Posté par: jac.fil le juillet 10, 2018, 01:04:04 pm
Après 2 mois de découverte du monde de l'Arduino, et donc un peu moins débutant, je suis en mesure de répondre à ma propre question ;) ;)  peut être cela pourra t-il, servir à quelqu'un ??

Il est facilement possible ( et je m'en sert contamment) de créer un tableau d'objets selon la syntaxe suivante

Nous avons une classe Led et instancié led1, led2, led3 le tableau est déclaré ainsi

Led tableau_led []={led1,led2,led3};

Il suffit ensuite d'appeler un objet classiquement : tableau_led[2].Allume(); par exemple.

Ces tableaux sont très pratiques dans une boucle, par exemple pour allumer toutes les leds

For (i=0; i<2;i++)
     {
         tableau_led.Allume();
      }

Voilà mon petit propos est peut être évident pour les spécialistes, mais cela m'aurait été bien utile il y 2 mois car malgré mes recherches je n'avais rien trouvé et j'ai donc essayé par moi même
Titre: Re : Tableau d'objet
Posté par: bobyAndCo le juillet 10, 2018, 03:48:53 pm
Pas bien cherché, il y a tout sur locoduino.org,

Ces tableaux qui peuvent nous simplifier le développement Arduino : http://www.locoduino.org/spip.php?article227 (http://www.locoduino.org/spip.php?article227)
Titre: Re : Tableau d'objet
Posté par: Jean-Luc le juillet 10, 2018, 04:26:31 pm
C++ est un langage compliqué avec des pièges à tous les coins de rues  :)

Et donc ta question initiale était un peu trop générale pour une réponse courte, ce qui explique le silence relatif.

Oui on peut faire un tableau d'objets. Oui les objets seront construits mais le constructeur appelé sera celui sans argument : on ne peut pas passer des argument lors de la construction de chaque objet d'un tableau (en fait si, dans le cas où le tableau est initialisé explicitement, voir http://forum.locoduino.org/index.php?topic=549.msg6577#msg6577).

Dans la manière dont tu t'y es pris : déclarer des objets puis les utiliser comme valeur d'initialisation de ton tableau, il y a 2 problèmes :

Pour le 1., la conséquence est que tu vas avoir tes objets en doublon. D'une part tes objets led1, led2 et led3 et d'autre part une copie dans le tableau. Donc une consommation de mémoire double et un risque de confusion : appeler une méthode de led1 et une méthode de tableau_led[0] ne s'adresse pas au même objet.

Pour le 2., copier un objet est fait selon différentes façons :

Qu'est ce que ça change ? Eh bien si l'objet à copier contient un membre qui est un pointeur vers un tableau alloué dynamiquement lors de la construction de l'objet, la copie brutale réalisera une copie du pointeur. Par conséquent, l'objet et ça copie partageront ce tableau alloué dynamiquement, ce qui n'est peut être pas ce que l'on veut. Dans ce cas, il faut définir un constructeur par recopie dont le rôle sera d'allouer un tableau et d'effectuer la copie du tableau. L'objet et sa copie ont donc leur propre tableau au lieu d'en partager un seul.

En résumé, faire :

Led led1;
Led led2;
Led led3;

Led tabl[] = { led1, led2, led3 };

Ne fait sans doute pas ce que tu voudrait. Si il est nécessaire d'avoir des arguments pour construire un objet, il vaut mieux avoir un objet Led avec une méthode permettant de l'initialiser à postériori, appelons la init. Donc il suffit d'écrire

Led tabl[3];

void setup()
{
  for (int i = 0; i < 3; i++) {
    tab[i].init(monArgumentQuiVaBien);
  }
}
Titre: Re : Tableau d'objet
Posté par: Pierre59 le juillet 10, 2018, 04:44:02 pm
Bonjour

Il faut se méfier des manipulations avec des objets, le programme suivant est une adaptation du programme, présenté ci-dessus, manipulant des leds :

#include <stdio.h>
#include <iostream>

class Led {
public:
int n;
Led(int x) { n=x; }
};

Led l1(1),l2(2),l3(3);
Led ls[]={l1,l2,l3};

int main(int argc, char **argv) { int i;
for (i=0; i<3; i++) {
printf("%d ",ls[i].n); //std::cout<<ls[i].n<<" ";
}
printf("\n"); //std::cout<<<<l2.n"\n";
l2.n=0;
printf("%d\n",l2.n); //std::cout<<"\n";
for (i=0; i<3; i++) {
printf("%d ",ls[i].n); //std::cout<<ls[i].n<<" ";
}
printf("\n"); //std::cout<<"\n";
return 0;
}

On a une classe Led avec juste une variable entière qui est un numéro de led.
On déclare 3 leds l1,l2,l3 puis un tableau de 3 leds ls.

Dans le main :
- on affiche le tableau de leds avec une boucle
- on modifie le numéro de la led2 en le mettant à 0 et on l'affiche (pour vérification)
- on réaffiche le tableau de leds

Voila le résultat :

1 2 3
0
1 2 3

Bizarre le tableau n'a pas changé (notamment la led2)  :(

C'est normal quand le tableau est créé une copie des leds est faite, donc si on modifie la led2 sa copie elle n'est pas modifiée.
C'est très pernicieux et cela peut créer des bugs difficiles à trouver.

Pour éviter ces problèmes il vaut mieux utiliser des pointeurs sur les objets. C'est d'ailleurs la bonne façon de faire, pour profiter de l'héritage et du polymorphisme.

Amicalement

Pierre

Titre: Re : Tableau d'objet
Posté par: savignyexpress le juillet 10, 2018, 04:46:56 pm
Merci beaucoup Jean-Luc pour tes précisions quant aux pièges du C++.  :)

Ta proposition de méthode init appelée explicitement par la fonction setup me paraît bien plus sûre que des constructeurs appelés on ne sait pas quand. On peut donc ignorer le constructeur par défaut dans ce cas là.

La manière de procéder proposée par Jac.Fil fonctionnerait pour autant que le tableau contienne des pointeurs vers les objets.

Meilleures salutations.

Marc-Henri
Titre: Re : Tableau d'objet
Posté par: Pierre59 le juillet 10, 2018, 04:52:30 pm
Bonjour

Voila la version avec des pointeurs de leds :

#include <stdio.h>
#include <iostream>

class Led {
public:
int n;
Led(int x) { n=x; }
};

Led *l1=new Led(1),*l2=new Led(2),*l3=new Led(3);
Led* ls[]={l1,l2,l3};

int main(int argc, char **argv) { int i;
for (i=0; i<3; i++) {
printf("%d ",ls[i]->n); //std::cout<<ls[i]->n<<" ";
}
printf("\n"); //std::cout<<<<l2->n"\n";
l2->n=0;
printf("%d\n",l2->n); //std::cout<<"\n";
for (i=0; i<3; i++) {
printf("%d ",ls[i]->n); //std::cout<<ls[i]->n<<" ";
}
printf("\n"); //std::cout<<"\n";
return 0;
}

et le résultat :

1 2 3
0
1 0 3

Le numéro de la led2 à bien été modifié aussi dans le tableau de leds.

Pierre
Titre: Re : Tableau d'objet
Posté par: Pierre59 le juillet 10, 2018, 04:55:00 pm
Désolé,mes messages se sont un peu télescopés avec celui de Jean-Luc.

Pierre
Titre: Re : Tableau d'objet
Posté par: Jean-Luc le juillet 10, 2018, 04:58:10 pm
Pas de soucis Pierre  ;)
Titre: Re : Re : Tableau d'objet
Posté par: Thierry le juillet 10, 2018, 05:35:27 pm
Ta proposition de méthode init appelée explicitement par la fonction setup me paraît bien plus sûre que des constructeurs appelés on ne sait pas quand. On peut donc ignorer le constructeur par défaut dans ce cas là.

Je rebondis maintenant que mon internet est revenu...
Ignorer le constructeur par défaut n'est juste pas possible. Il est forcément appelé au moment de l'initialisation, et il est toujours présent ! Soit il est fourni par le programmeur, soit le compilateur en fourni un d'office qui ne fait rien. Quelle que soit la classe, je fais toujours un constructeur dit 'par défaut' qui va initialiser toutes les données membres de la classe à des valeurs bateau, généralement 0 ou NULL selon le type. Cela n'empêche nullement la solution de la fonction init, c'est juste un complément qui assure que les variables dans l'instance de la classe, les données membres ont une valeur connue et déterminée. Si on ne fait rien pour les initialiser, le contenu de chaque variable sera ce que la mémoire contient à leur emplacement au moment de la création de l'objet... ET si on oublie le init() ou que l'on fait quelque chose avant, on est pas du tout sur de ce qui va se passer. Pire, ce ne sera pas reproductible : un jour ça marche, le lendemain ça ne marche plus !

Donc la classe Led de Pierre deviendrait
class Led {
public:
int n;

Led() { n=0; }  // constructeur par défaut.
Led(int x) { n=x; }
};

Led MaLed;  // la donnée membre 'n' de MaLed est initialisée à 0. C'est probablement faux, mais c'est toujours faux de la même manière !
Titre: Re : Tableau d'objet
Posté par: Pierre59 le juillet 10, 2018, 05:56:46 pm
Bonjour

Il me semble que le constructeur pas défaut n'est pas ajouté par le compilateur si un autre constructeur est fourni. Chaque constructeur fourni doit alors initialiser les variables.

Pierre
Titre: Re : Tableau d'objet
Posté par: Thierry le juillet 10, 2018, 06:51:53 pm
Après vérification, tu as raison. Le constructeur par défaut n'est créé automatiquement par le langage que si aucun autre n'existe !
Titre: Re : Tableau d'objet
Posté par: savignyexpress le juillet 11, 2018, 06:27:29 am
Merci à tous pour vos précisions relatives aux constructeurs, un sujet que je ne connais pas bien.

Belle journée et meilleures salutations.

Marc-Henri
Titre: Re : Tableau d'objet
Posté par: jac.fil le juillet 14, 2018, 08:36:22 am
Je ne pensais pas que ma modeste contribution allait déclencher autant de réponses

Je vais les lire tranquillement et essayer de comprendre, mais vous êtes tous partis dans des sphères qui dépassent très largement mes modestes connaissances en C++......

Je remercie tous les contributeurs qui ont pris le temps de répondre.

Je sais que mon code n'est pas très pur, mais pour l'instant la gestion de mes cantons fonctionne parfaitement et rempli parfaitement son office. 3 convois se suivent sans aucun problème sur mon réseau.

Ceci étant, il faut constamment essayer de se former et s'améliorer  :) :)
Titre: Re : Tableau d'objet
Posté par: Jean-Luc le juillet 14, 2018, 09:50:46 am
Une dernière solution qui évite de cosommer plus de mémoire que nécessaire mais qui n'est applicable que pour un tableau alloué statiquement est de référencer directement le constructeur dans l'initialisation du tableau. Ceci infirme une de mes affirmation précédentes. Je pense que cette façon de faire n'est apparue qu'avec C++11.

Comme ceci :

class Led {
  private: uint8_t mPin;
  public: Led(uint8_t inPin) : mPin(inPin) {}
  public: void display() { Serial.println(mPin); }
};

Led ledTab[] = {
  Led(10),
  Led(8),
  Led(3),
  Led(2)
};


void setup()
{
  Serial.begin(115200);
  for (int i = 0; i < 4; i++) {
    ledTab[i].display();
  }
}

void loop()
{
}
Titre: Re : Tableau d'objet
Posté par: Pierre59 le juillet 15, 2018, 10:09:14 am
Bonjour

Le programme précédent avec 4 leds fonctionne très bien tel quel, mais si on veut le faire évoluer les choses vont se compliquer. Voici deux exemples entre autres qui vont poser problème :

Premier cas

Supposons que je veuille faire quelques traitements sur une led, par exemple la deuxième, comme on le recommande je vais utiliser une variable intermédiaire pour économiser des indiçages superflus, donc écrire quelque chose comme cela :

Led led=ledTab[1];

On a fait une copie sans trop s'en rendre compte, c'est "foutu" les leds du tableau ne seront pas modifiées si on modifie la copie.

Deuxième cas

Le programme précédent ayant l'air de fonctionner (et il fonctionne), en tant que "débutant" je m'enhardi en essayant de faire de l'héritage, de façon classique et comme dans les articles sur les objets de Locoduino, j'essaye de faire une LedBicouleur héritant de Led. Cela donne quelque chose comme cela :

class Led {
  protected: int mPin;
  public: Led(int inPin) : mPin(inPin) {}
  public: void display() { printf("%d ",mPin); }
};

class LedBicouleur : public Led {
  private: int mPin2; // deuxieme pin
  public: LedBicouleur(int inPin,int inPin2) : Led(inPin),mPin2(inPin2) {}
  public: void display() { printf("%d-%d ",mPin,mPin2);  }
};

Led ledTab[] = {
  Led(10),
  LedBicouleur(8,9),
  Led(3),
  Led(2)
};

int main(int argc, char **argv) {
  for (int i = 0; i < 4; i++) {
    ledTab[i].display();
   }
}

Comme je tiens à pouvoir facilement exécuter les programmes, c'est écrit en C++ standard (pas spécialement pour l'Arduino), donc il me faut adapter les entrées/sorties et avoir une fonction main à la place du setup(), en plus j'ai changé les uint8_t en int (pour la lisibilité). J'ai du passer aussi la variable mPin de Led de private à protected (pour l'héritage).

La classe LedBicouleur hérite de la classe Led, elle rajoute une variable pour la deuxième pin, un constructeur comme il faut et une nouvelle méthode display() pour afficher les deux pins.

Dans le tableau de leds j'ai changé une Led en LedBicouleur. On exécute, cela donne :

10 8 3 2

Grosse déception, les deux pins de la LedBicouleur ne sont pas affichées  :(

En tant qu'informaticien le tableau de leds me pose un problème une LedBicouleur prend plus de place qu'une Led (un entier de plus), le compilateur n'a que deux possibilités, soit il tronque la LedBicouleur à la taille de Led, soit il alloue de la place pour 4 LedBicouleur. Après vérifications (sizeof) il me semble que le compilateur tronque.

Bilan

La BONNE solution pour éviter ces problèmes (et d'autres) c'est d'utiliser systématiquement des pointeurs sur les objets quand on veut les manipuler. Voila ce que cela donne :

class Led {
  protected: int mPin; float x;
  public: Led(int inPin) : mPin(inPin) {}
  public: virtual void display() { printf("%d ",mPin); }
};

class LedBicouleur : public Led {
  private: int mPin2; double x,y,z; // deuxieme pin
  public: LedBicouleur(int inPin,int inPin2) : Led(inPin),mPin2(inPin2) {}
  public: virtual void display() { printf("%d-%d ",mPin,mPin2);  }
};

Led* ledTab[] = {
  new Led(10),
  new LedBicouleur(8,9),
  new Led(3),
  new Led(2)
};

int main(int argc, char **argv) {
  for (int i = 0; i < 4; i++) {
    ledTab[i]->display();
   }
}

Cela affiche :

10 8-9 3 2

Les deux objets n'ont pas été modifiés (à part l'ajout de "virtual" à la méthode display() (nécessaires pour le polymorphisme)
Le tableau de Leds à été transformé en tableau de pointeurs de Leds, et le programme de test (main()) à été modifié en conséquence.

Amicalement

Pierre
Titre: Re : Tableau d'objet
Posté par: jac.fil le juillet 16, 2018, 01:21:15 pm
Initiateur néophyte de cette discussion , j'ai lu attentivement tous vos posts.

Je suis bien persuadé à présent grâce à vous tous qu'il faut passer par des pointeurs pour les tableaux et j'ai par ailleurs bien étudié les pointeurs et leur rôle me semble moins obscur à défaut de les maîtriser complètement (lourde tâche !!!)

Certain d'entre vous utilise Led* avant de déclarer le tableau , quel est la signification ??

Merci d'avance pour votre retour.

PS : au fait en réponse à bodyAndCo

Pas bien cherché, il y a tout sur locoduino.org,

Ces tableaux qui peuvent nous simplifier le développement Arduino : http://www.locoduino.org/spip.phparticle227 (http://www.locoduino.org/spip.php?article227)
J'avais bien lu cet article, mais celui ci n'aborde pas les objets d'où ma question et étant donné les discussions qu'elle a fait naître on est vraiment très loin de celui-ci ,  fort bien fait par ailleurs pour expliquer l'utilisation classique des tableaux avec les variables habituelles du C

Titre: Re : Tableau d'objet
Posté par: Pierre59 le juillet 16, 2018, 01:55:19 pm

Bonjour

Faut pas trop se polariser sur les pointeurs, certes c'est à la base de pas mal de choses en C et C++ (par exemple les tableaux, quand on écrit t[j] le compilateur voir cela comme *(t+j)  donc des pointeurs), mais bien qu'ayant enseigné le C et le C++ pendant de nombreuses années, j'utilise au minimum les pointeurs pour des problèmes de lisibilité des programmes.

L'utilisation de pointeurs sur les objets, c'est plutôt simple :

- pour la déclaration on met une étoile derrière le type, par exemple Led devient Led* (Led c'est un objet, Led* c'est un pointeur sur un objet)

- pour l'utilisation des objets au lieu de mettre objet.variable ou objet.fonction() on met objet->variable ou objet->fonction(), c'est à peu près tout (et on peut quasiment oublier les pointeurs).

Pierre
Titre: Re : Tableau d'objet
Posté par: jac.fil le juillet 16, 2018, 04:03:10 pm
Merci Marc-Henry pour le retour aussi rapidement et surtout pour les explications.

Du coup cela conforte mon idée sur les modifications à apporter à mon programme, et surtout si cela fonctionne, ca ne bouleverse pas toute sa structure.

Je vais essayer cela mais après 2 mois  d'électronique et de programmation, je me change un peu les idées en faisant du décor, c'est quand même du modélisme ferroviaire la base de notre hobby  ;) ;)


Très cordialement
Titre: Re : Tableau d'objet
Posté par: jac.fil le juillet 16, 2018, 07:08:17 pm
Rendons à César ......
Merci Pierre et non pas Marc Henry pour la réponse d' aujourd'hui , désolé de mon erreur, ces pointeurs me perturbent  ;D ;D ;D ;D