Objet d'une classe en paramètre dans une autre classe

Bonjour à la communauté.

Je cherche, depuis plusieurs jours à faire passer en paramètre un objet d’une classe vers une autre classe.

Un exemple que je cherche à faire :

main.ino

#include <LiquidCrystal.h>
#include "menuLCD.h"

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
MenuLCD menu(lcd); //Ici, je cherche à faire passer en paramètre l'objet lcd de façon à pouvoir utiliser lcd.clear()...

void setup () {
  Serial.begin(9600);
  Serial.flush();

  lcd.begin(16, 2);
}

void loop() {
 menu.start(1); //Ici, j'utiliserais des objets de la classe MenuLCD
 lcd.println("Hello world"); //Ici, j'utilise lcd comme d'habitude
}

menuLCD.h

#ifndef menuLCD_h
#define menuLCD_h

class MenuLCD
{
  public :
    MenuLCD(void);  //Constructor //Ici, je ne sais pas comment déclarer pour recevoir l'objet lcd
    start(int level);
  private :
};

#endif

menuLCD.cpp

#include "menuLCD.h"

MenuLCD::MenuLCD(void) //ici, comment reprendre la déclaration de lcd
{
    lcd.clear(); //Ici, on peut utiliser l'objet transmis  
}

MenuLCD::start(int level)
{
   lcd.clear();
   lcd.setCursor(0,0); //Colonne, Ligne
   lcd.print("menu");
   lcd.println(level);
}

De ce que j’ai lu, sans savoir comment le faire, il faut passer par des pointeurs, ors il existe soit “*” soit “&”. Enfin, comment l’intégrer ?

Bonjour,

Il faut que tu crées un constructeur qui prenne comme paramètre une référence vers ton instance lcd.

On est bien d'accord avec ça !

Mais quelle type de paramètre ? C'est pas un integer, c'est pas un String...

En gros:

public :
    MenuLCD(type *lcd);  //Constructor //Ici, je ne sais pas comment déclarer pour recevoir l'objet lcd
    //ou
    MenuLCD(type &lcd);  //Constructor //Ici, je ne sais pas comment déclarer pour recevoir l'objet lcd

Tu peux passer par un passage de paramètre par référence et conserver la référence dans les membres privés de la classe :

#ifndef menuLCD_h
#define menuLCD_h

#include <LiquidCrystal.h>

class MenuLCD
{
  private:
    LiquidCrystal &myLcd;

  public :
    MenuLCD(LiquidCrystal &lcd);
    start(int level);
};

#endif
#include "menuLCD.h"

MenuLCD::MenuLCD(LiquidCrystal &lcd)
{
  myLcd = lcd;
  myLcd.clear();
}

MenuLCD::start(int level)
{
  myLcd.clear();
  myLcd.setCursor(0, 0); //Colonne, Ligne
  myLcd.print("menu");
  myLcd.println(level);
}

L'appel à lcd.begin() est dans le setup.

Le LCD est utilisé dans le constructeur : myLcd.clear().

Chronologiquement ce n'est pas OK. Il vaudrait mieux ne pas appeler de méthode de lcd dans le constructeur.

En effet, ça ne fonctionne pas… Mais, chronologiquement non plus :confused:

menuLCD.h

#ifndef menuLCD_h
#define menuLCD_h

#include "ExaNumericLcdMidasI2C.h"


class MenuLCD
{
  public :
    MenuLCD(LcdMidasI2c &lcd);  //Constructor
    getTest(void);
  private :
    LcdMidasI2c &_lcd;  //LCD MIDAS I2C Librarie ExaNumericLcdMidasI2C
};

#endif

menuLCD.cpp

#include "menuLCD.h"

MenuLCD::MenuLCD(LcdMidasI2c &lcd)
{
  _lcd = lcd;
}

MenuLCD::getTest(void)
{
  _lcd.clear(); 
  _lcd.setCursor(0,0); //Colonne, Ligne
  _lcd.print("Hello world");
}

main.ino

#include <LiquidCrystal.h>
#include "menuLCD.h"

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
MenuLCD menuUser(lcd); //Ici, je cherche à faire passer en paramètre l'objet lcd de façon à pouvoir utiliser 

void setup () {
  Serial.begin(9600);
  Serial.flush();

  lcd.begin(16, 2);
}

void loop() {
 menuUser.getTest();
}

Sauf que rien ne s’affiche

Et hop on passe d'un LCD à un LCD I2C ...

LcdMidasI2c : connais pas. Quelle librairie ?

Tu as essayé d'afficher avec un sketch simple ?

Il faut que la class de l’instance que tu passes dans le constructeur soit la même (ou une class héritée) que celle déclarée dans le constructeur.
Dans le constructeur tu as une class LcdMidasI2c

   MenuLCD(LcdMidasI2c &lcd);  //Constructor

et tu lui passes un objet de class LiquidCrystal

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
MenuLCD menuUser(lcd); //Ici, je cherche à faire passer en paramètre l'objet lcd de façon à pouvoir utiliser

Étonnant que cela puisse compiler non ?

Je reprends :

hbachetti:
Et hop on passe d'un LCD à un LCD I2C ...

LcdMidasI2c : connais pas. Quelle librairie ?

Tu as essayé d'afficher avec un sketch simple ?

C'est la même librarie que liquidSchristal, j'ai juste refais mes appels pour correspondre a celui que j'utilise. Mêmes fonctions, même nom, mêmes variables toussa.

hbachetti:
Étonnant que cela puisse compiler non ?

En effet ! Je suis même surpris que ça fonctionne en partie...

Ce qui me fais pensé à:
1- est-ce que je pointe au bon endroit ?
2- est-ce que le contenu de mon pointeur à changé ?
3- comment vérifier que je passe bien l'instance créé dans le main, dans ma classe ?
4- existe-t-il une solution plus adhéquate ?

Bref, je suis dans le doute :roll_eyes:

Sans le code de ExaNumericLcdMidasI2C.h impossible de dire quoi que ce soit.

Voilà donc le .h de la classe LCD.

Et bien…
Ça fonctionne !

En fait, c’est un problème de pointeur. J’ai fini par me poser la question, est-ce que j’affecte le pointeur avec le bon endroit ? Et la réponse était pire, le pointeur n’étais tout simplement pas rempli. Rien à voir avec la classe exa… Bref !

Je met la solution au cas ou ça en intéresserais d’autres :
Donc, mon main, réduit pour les tests :

#include <LiquidCrystal.h>
#include "menuLCD.h"        //Le chemin de ma classe qui héritera de l'objet lcd

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
MenuLCD menuUser(lcd); //Ici, nous instancions la classe MenuLCD avec l'objet lcd passé en paramètre pointeur

void setup () {
  Serial.begin(9600);
  Serial.flush();

  lcd.begin(16, 2); //Utilisation de la classe LiquidChristal ou de exa.. Nombre de colonnes 16, nombre de ligne 2

  menuUser.getTest(); //ici, nous affichons le contenu de getTest() qui fera appel à l'objet passé en paramètre
}

void loop() {
 //Vide pour les essais !
}

Voyons, donc le .h de ma classe:

#ifndef menuLCD_h
#define menuLCD_h

#include <LiquidCrystal.h>  //La librairie parente pour laquelle nous cherchons à utiliser ses membres

class MenuLCD
{
  public :
    MenuLCD(LiquidCrystal &lcd);  //Constructeur avec passage en paramètre de l'objet au travers d'un pointeur
    void getTest(void);
    
  private :
    LiquidCrystal *_lcd;  //L'instanciation de la classe liquidCrystal avec comme paramètres, ceux de l'objet pointé
};

La différence était ici… dans la partie private ! mais pas que…

Voyons donc le .cpp

#include "menuLCD.h"

MenuLCD::MenuLCD(LiquidCrystal &lcd) //Passage en paramètre de l'objet
{
  _lcd = &lcd; //Nous remplissons le pointeur avec l'objet pointé
}

void MenuLCD::getTest(void)
{
  _lcd->clear(); //Autre subtilité, nous ne passons plus par un "." mais par "->"... Car on fait référence à l'objet pointé
  _lcd->setCursor(0,0); //Colonne, Ligne
  _lcd->print("Hello world");
}

Bien entendu, ça fonctionne parfaitement !

Je peux donc clore ce tread…

Tu utilises inutilement par des pointeurs.

C'est quasiment identique à la solution présentée par hbachetti au post #3 sauf que sa solution est beaucoup plus élégante.

Je n'avais pas stocké le lcd dans la classe sous forme de pointeur, mais de référence.
Cela permettait entre autres de ne pas transformer tout tes appels _lcd.clear(), _lcd.setCursor(), etc. en _lcd->clear(), lcd->setCursor, etc.
Mais bon, cela revient au même.

Alors, que ce passe-t-il avec l'autre solution ?
Par ce que, du coup... ça jette un doute ! En effet, si c'est pas élégant, c'est qu'il y a moyen d'optimiser le code et là, ou ouvre la suite du sujet.

Ma question sera donc, comment modifier le code qui fonctionne par celui qui devrais fonctionné et qui est élégant.

C'est intéressant de creuser ce sujet.

De l'extérieur ce n'est pas grave :wink: ta solution fonctionne bien, c'est juste qu'en général on fait un choix de notation : référence ou pointeur. Dans ton code tu as les 2 notations (passage par référence de l'objet mais sa mémorisation est faite avec un pointeur). Cela n'impacte pas sur l'efficacité du code, c'est juste que le code est un poil plus compliqué à lire et qu'on se pose la question pourquoi ce passage de notation "référence" à notation "pointeur". Si tu veux que cela soit parfait fait comme hbachetti, n'utilise que des références (plus d'infos sur les références : Cours de C/C++).

Tout à fait.
Juste une question d'écriture. C'est plus facile de taper _lcd.clear() que _lcd->clear().

    LiquidCrystal &_lcd;  // référence
    LiquidCrystal *_lcd;  // pointeur

    _lcd.clear();    // référence
    _lcd->clear(); // pointeur

Ok pour ces informations.

Mais, alors pourquoi ça compile et ça plante le µC quand j'utilise les références ?

Joue au jeu des 7 erreurs entre ton code avec référence et celui de hbachetti. Il y en a forcément au moins une :wink: