Besoin de stocker des types différents dans une variable

Bonjour à tou·te·s

J'ai besoin de stocker, dans la variable d'une classe, une classe variante. Par exemple, MaClasse.ma_variable doit pouvoir être à la fois MaClasse.ma_variable = MaClasseA() ou MaClasse.ma_variable = MaClasseB()

Originaire de python, je tatonne un peu avec le code pour reproduire ce genre de cas, j'ai pu obtenir un code de ce style, mais le repr correspond toujours à celui de ma classe AbstractTimer. Auriez-vous un idée de comment corriger ça svp?

test.ino

#include <Arduino.h>
#include "player.h"

Player player_01;

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

  player_01.type_index = 1;
  player_01.update_timer();
  Serial.println(player_01.type_index);
  Serial.println(player_01.timer->repr);
}

void loop() {

}

player.h

#ifndef Player_h
#define Player_h

#include <Arduino.h>
#include "timers.h"


class Player {
  private:
    AbstractTimer* timers[2];

  public:
    int type_index;
    AbstractTimer* timer;

    Player(int type_index_ = 0);

    ~Player() {
      for (int i = 0; i < 2; i++) {
        delete timers[i];
      }
    }

    void update_timer();
};

#endif

player.cpp

#include <Arduino.h>
#include "player.h"
#include "timers.h"

Player::Player(int type_index_) : type_index(type_index_) {
  timers[0] = new FischerTimer();
  timers[1] = new BronsteinTimer();
}

void Player::update_timer() {
  timer = timers[type_index];
  Serial.print("Updated Timer: ");
  Serial.println(timer->repr);
}

timers.cpp

#include <Arduino.h>
#include "timers.h"


String AbstractTimer::repr = "<AbstractTimer>";
String FischerTimer::repr = "<FischerTimer>";
String BronsteinTimer::repr = "<BronsteinTimer>";

timers.h

#ifndef AbstractTimer_h
#define AbstractTimer_h

#include <Arduino.h>


class AbstractTimer {
  public:
    static String repr;
};


class FischerTimer : public AbstractTimer {
  public:
    static String repr;
};


class BronsteinTimer : public AbstractTimer {
  public:
    static String repr;
};



#endif

Merci d'avance!!

Ce n'est pas tout a fait clair ce que tu veux faire. Ça ressemble à du polymorphisme. Mais le polymorphisme implique que les fonctions membres publiques soient différentes d'une classe fille par rapport à la classe mère ou aux classes soeures. Faudrait un peu affiner les choses et voir s'il n'y a pas une solution plus simple à ton blème.
Parce que retrouver le même static String repr;dans toutes les classes ne permet pas au compilateur de s'y retrouver.....

On ne voit pas où tu initialises le membre repr ?
Il n'y a pas tout le code...

Oui c'est le but, le C est typé, donc ta variable aussi.
La question est doit tu utiliser une classe abstraite.
Donc que veux tu faire avec tes classeA et classeB.
Il y a-t-il une zone commune entre ses deux classes?

ta variable repr ne devrait elle pas être une variable d'instance public qui est initialisé dans le constructeur de la classes?
Car de ce que j'ai compris, tes classes on le même prototypage, c'est uniquement leur comportement interne qui change.
Donc ton programme n'a pas forcément besoin de connaitre son type, uniquement qu'elle implémente la classe abstraite quelle implémente

Je ne vois pas l'intérêt de mettre 2 timers dans un tableau.
Si tu avais 2 instances bien différenciées ce serait plus clair.
Imaginons que tu aies une méthode update dans la classe abstraite. Il est quand même plus clair d'appeler :

  fischerTimer.update();
  bronsteinTimer.update();

plutôt que :

timers[0].update();
timers[1].update();

ou :

  for (int i = 0; i < 2; i++) {
    timers[i].update();
  }

En utilisant deux instances bien distinctes, ton code marche.

Ce que tu cherches à faire existe tout fait : les RTTI (Run-time type information).
Exemple :

  #include <typeinfo>

  FischerTimer fTimer:
  Serial.println(typeid(fTimer).name());

Mais il faut enlever l'option -fno-rtti (voir platform.txt)

l'idée de la classe parent et d'utiliser des classes filles est bonne.

je proposerai un truc comme cela

ou l'appel à representation() est une fonction pure virtual qui laisse donc les détails de l'implémentation aux classes filles.

il suffit alors de proposer une implémentation différenciée pour chaque classe

const char * const FischerTimer::representation() const {
    return "Fischer";
}


const char * const BronsteinTimer::representation() const {
    return "Bronstein";
}

comme j'ai mis des traces dans le constructor il faut que le port série soit initialisé donc j'ai déplacé la création de l'instance de Player dans le .ino pour la mettre dans le setup, après le begin du port série.

ça permet ainsi de voir les étapes de construction et destruction ➜ on voit

constructeur de Abstract
constructeur de Fischer
constructeur de Abstract
constructeur de Bronstein
Updated Timer: Bronstein
1
Bronstein
destructor pour Fischer
destructor pour Bronstein
1 Like

Dans le timers.h ne doit tu pas déclarer le repr en virtual pour la classe mère afin que le polymorphisme fonctionne ?
Un bon petit cours ici https://openclassrooms.com/fr/courses/7137751-programmez-en-oriente-objet-avec-c/7710051-mettez-en-oeuvre-le-polymorphisme

1 Like

ça n'existe pas ça

M'enfin, il ne faut pas déclarer un membre repr dans les classes dérivées, ce membre existe déjà dans la classe parente ! Les classes filles en héritent tout à fait naturellement.
Dans le ctor de la classe parente tu ne t'en occupes pas, et dans le ctor des classes dérivées tu l'initialises à la bonne valeur.

Ca ne peut pas être une variable statique de la classe parente sinon les filles auraient le même nom.

Si vous mettez une variable normale alors vous mangez de la mémoire pour chaque instance et ce qu’essayait de proposer @anon66955792 c’était une variable statique virtuelle mais ce concept n’existe pas.

Le plus simple reste à mon avis la fonction pure virtual qui retourne le nom comme dans le wokwi

Comme la variable timer est un pointeur sur une instance de la classe AbsractTimer,
AbstractTimer est affiché malgré que ce soit un pointeur.
Je pense en effet que la solution de J-M-L est probablement la seule valable.

l'avantage d'utiliser une fonction plutôt que d'accéder directement à une variable publique de la classe c'est que vous conservez l'encapsulation.

et en déclarant tout constant on aide l'optimiseur qui va sans doute juste remplacer l'appel à la fonction par la valeur constante du pointeur retournée

const char * const FischerTimer::representation() const {
    return "Fischer";
}

Pour mémoire :

  • Les deux premiers const indiquent que la méthode representation() renvoie un pointeur constant vers un caractère constant (rien ne peut être modifié par ce pointeur)

  • Le troisième const s'applique à la méthode elle-même et précise que la méthode representation() est une méthode constante de la classe FischerTimer. Cela signifie que lorsque vous appelez cette méthode sur un objet de FischerTimer, cet objet n'est pas modifié par la méthode. Les membres de l'objet ne peuvent pas être modifiés à l'intérieur de la méthode. C'est une garantie que cette méthode n'altérera pas l'état interne de l'objet.

➜ avec cet info l'optimiseur doit se débrouiller et ça ne devrait donc pas coûter plus cher en temps CPU que d'accéder en direct au pointeur sur le texte.

Oups, j'avais zappé le "static" !
Bon, j'ai pas saisi tous les détails du pourquoi il faut nommer les classes, mais à mon sens quand une classe a besoin de connaître son propre type par une String, y'a un problème de conception plus général ?
On a une classe parente, virtuelle pure qui établit une interface, et des classes filles qui implémentent les fonctions virtuelles à leur façon. C'est le schéma général de la programmation objet, qu'est-ce qui ici nécessite ces complications ?
Et pour la mémoire, autant remplacer la String par un enum, non ?

Je pense que ce n'est pas le besoin, le besoin de fond est dans le titre, @Kraosor veut pouvoir mettre dans son tableau différents types de variables ➜ c'est plus pour un debug, s'assurer que le polymorphisme fonctionne comme attendu

Avec le wokwi on voit bien que le tableau timers

  timers[0] = new FischerTimer();
  timers[1] = new BronsteinTimer();

contient bien des instances différenciées et qu'on peut utiliser l'interface commune de la classe parent pour leur parler sans savoir qui est qui

Ces 14 réponses n'ont pas eu l'air d'éveiller l'intérêt de Kraosor.
Il est peut-être retourné à Python ???

:wink: ...

Bonsoir à toutes et tous.

Merci pour ces réponses, comme l'a très bien dit J-M-L, la méthode "repr" me sert juste pour le debug pour vérifier que je récupère la bonne classe. Je ne connaissais pas les RTTI, je teste demain pour voir ce que ça donne mais ça me semble prometteur (d'autant que je ne suis pas fier de cette méthode "repr" mais j'avais besoin de visualiser les choses plus clairement).

Le fait de stocker les deux classes différentes dans une liste sert juste à obtenir une correspondance facile car le choix de la classe se fait via un potentiomètre. Le but étant d'avoir contenue dans une variable une des deux classes mais sans besoin de savoir précisément laquelle c'est car les actions effectuées par la suite sont génériques et seuls quelques cas diffèrent en fonction des classes.

Merci beaucoup pour le wokwi, je vais analyser tout ça demain!

Je vais aussi réfléchir en parallèle à un changement d'architecture (l'exemple donné est bien entendu simplifié pour garder en lisibilité sur le problème actif). Je pense virer la classe Player() et faire en sorte que l'initialisation de la classe se fasse dans la loop en fonction des choix de l'utilisateur. Ainsi je pourrais avoir sans soucis une variable timer qui correspond à la classe désirée et non essayer de créer une variable polymorphique. Peut-etre que ça gagnera en lisibilité.

Ca semble top! Merci beaucoup!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.