J'ai une classe dont l'un des attributs est le nom, je voudrais que la création de cette classe se fasse à base de son attribut nom et qu'elle soit unique.
J'ai une classe dont l'un des attributs est le nom, je voudrais que la création de cette classe se fasse à base de son attribut nom et qu'elle soit unique.
Il faudra 1 peu + de précision
Parce que si la classe a un attribut, cela veut dire qu'elle est déjà créée (au moins 1 appel d'1 constructeur)donc impossible d'ajouter des membres ni des méthodes.
Donc, on peut penser à une initialisation paresseuse ("lazy initialisation") : avec soit des membres dérivants tous d'1 même classe mère, soit des membres de type void* (<- la généricité du C avec la lourdeur que cela engendre)
En fait, après avoir créé la classe dont l'un des attributs est une chaîne de caractère, on me demande dans le menu principal de faire en sorte que lorsque l'utilisateur crée une classe sur la base de son attribut chaîne de caractère que cela soit unique, qu'il n'existe aucune classe qu'on ait créé auparavant qui a ce même attribut chaîne de caractère.
Salut,
Le fait est que tu peux empêcher l'utilisateur de ta classe de faire pas mal de choses, comme de copier une instance de ta classe, ou comme assigner les valeurs d'une instance à une autre instance existante, mais tu ne peux pas l'empêcher "comme cela" de créer spécifiquement deux instances de la classe dont l'un des attributs serait identique.
Laisses mois te montrer:L'implémentation des fonctions qui sont nécessaires (le constructeur le l'accesseur name) prendrait la forme de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 //soit la classe class MaClasse{ public: /* je veux que l'utilisateur nous donne le nom sous lequel l'instance sera connue */ MaClasse(std::string const & name); /* je veux empêcher l'utilisateur de créer une copie */ MaClasse(MaClasse const &) = delete; /* je veux empêcher l'utilisateur d'assigner les valeurs d'une instance à l'autre */ MaClasse & operator=(MaClasse const &) = delete; /* Ma classe a visiblement sémantique d'entité. Comme je ne sais pas si * on va créer une hiérarchie de classes, autant prévoir que ce sera fait */ virtual ~MaClasse() = default; /* Je veux pouvoir accéder (en lecture seule) au nom */ std::string const & name() const; private: /* l'attribut de nom */ std::string /* const */ name_; };
Sous cette forme, le compilateur "engueulera" l'utilisateur de la classe s'il essaye de créer une copie ou d'affecter les valeurs d'une instance à une autre instance existante, par exemple, le code qui suit ne compilera pas
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 MaClasse::MaClasse(std::string const & name):name_{name}{ /* Je n'ai aucun moyen simple, ici, de vérifier si la * valeur de name_ n'est pas déjà utilisée par une autre * instance de ma classe */ } std::string const & MaClasse::name() const{ return name_; }
C'est déjà pas mal, hein?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 int main(){ MaClasse martin{"Martin"}; // OK MaClasse henry{"Henry"}; // OK MaClasse copie{martin}; // essaye de copier l'instance martin: refusé martin = henry; // essaye d'assigner les valeurs de henry à celles de martin : refusé }
Mais, comme je l'ai dit dans le commentaire du constructeur de MaClasse, je n'ai aucun moyen simple de savoir si la valeur transmise en paramètre pour représenter l'attribut name n'a pas déjà été utilisé pour une autre instance. Le code qui vient va donc être accepté et fonctionner (pour notre maleur):Alors, bien sur, il existe des solutions. Mais ces solutions ne passent pas par le code de la classe MaClasse. Et cela pour une simple et bonne raison: quand on exécute le constructeur de MaClasse, il est déjà trop tard: la décision de créer une nouvelle instance a déjà été prise,et on ne peut pas revenir en arrière (enfin, si, on pourrait, et de différentes manières d'ailleurs, mais ce ne serait tout simplement "pas logique"
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 int maiin(){ MaClasse martin{"Martin"}; MaClasse autre{"Martin"}; // OOUPSS std::cout<<"nom de l'instance martin "<<martin.name()<<"\n" <<"nom de l'instance autre "<<autre.name()<<"\n"; // HEUU... C'est moche... // Car on aurait voulu que le nom "Martin" soit unique }).
Par contre, rien ne nous empêche de mettre en place une logique qui refuse de créer une nouvelle instance de la classe si les conditions ne sont pas remplies.
Par exemple, lorsque l'utilisateur veut créer une nouvelle instance, on peut lui demander le nom qui devra être utilisé, et vérifier si ce nom est déjà utilisé par une des instances qui existent déjà. Si le nom n'existe pas, on peut créer la nouvelle instance, et, si le nom existe déjà, on refuse de la créer, et on a sans doute intérêt à afficher un message explicite à l'utilisateur.
La grosse question sera alors :Mais, avant de répondre à cette question, il y en a une autre encore plus importante à se poser:comment savoir si le nom introduit par l'utilisateur existe déjà
Commençons donc par répondre à la deuxième question:comment puis-je faire pour représenter un nombre inconnu à l'avance d'instances de ma classe![]()
Pour pouvoir représenter et maintenir en mémoire un nombre inconnu à l'avance d'instances de MaClasse, nous devrions sans doute utiliser une collection "de taille dynamique".
La réponse à la première question tombe alors "sous le sens" : Une fois que l'on sait quel nom l'utilisateur veut ajouter, on peut vérifier dans la collection utilisée pour représenter les instances de la classe qui ont déjà été créées si... le nom demandé par l'utilisateur existe déjà Et la logique à mettre en oeuvre pourrait donc prendre la forme de
- Demander le nom à ajouter
- vérifier si le nom demandé existe déjà
- si le nom existe déjà
- Afficher un message de refus et recommencer en (1)
- sinon
- ajouter une nouvelle instance à la collection
La collection la plus couramment utilisée pour représenter "un nombre inconnu à l'avance d'éléments " est sans doute la classe std::vector. Elle pourrait très bien faire l'affaire
Cependant, ce type de collection présente, certes, de nombreux avantages, il n'en demeure pas moins qu'il présente également de nombreux inconvénients. Le principal d'entre eux -- dans le cadre qui nous occupe -- étant la lenteur de la recherche, qui se fait classiquement avec une complexité en O(N) (comprends: il faut, dans le pire des cas, parcourir N des N éléments que std::vector contient pour trouver l'élément que l'on recherche).
Tu pourrais donc tout aussi bien utiliser des collections qui permettent une recherche beaucoup plus rapide car s'effectuant avec une complexité en O(log(N)); c'est à dire qui permettent de supprimer la moitié des éléments restants à chaque fois que l'on vérifie un élément ne correspondant pas à celui que l'on recherche (on parle de recherche "dichotomique").
La bibliothèque standard met deux classes de ce genre à notre disposition: la classe std::set et la classe std::map.
La différence entre les deux étant que chaque élément appartenant à un std::set sert -- de lui-même -- de "clé de tri" pour le std::set. On ne peut donc pas modifier l'élément facilement. Alors que std::map va utiliser une clé (qui permet le tri et qui ne peut donc pas être modifiée) et une valeur (qui peut être modifiée).
Les deux pourraient s'avérer utiles ici, du fait que ma classe MaClasse est particulièrement minimaliste. Mais si on prévoyait de pouvoir modifier quelques valeurs appartenant à cette classe (à l'exception bien sur de l'attribut name_ ), l'utilisation d'une std::map serait sans doute préférable
Pour le reste, il y aura quelques fonctions à mettre en place, parmi lesquelles on pourrait citer:
- afficherMenu : se contente d'afficher un menu offrant le choix de la "prochaine action" à l'utilisateur
- extraireChoix : récupère le choix de la "prochaine action" introduit par l'utilisateur. Devra s'assurer que ce que l'utilisateur a introduit est "cohérent" avec le choix proposé
- demanderNom : demande le nom qui devra être utilisé pour la nouvelle instance de la classe
- nomExiste : vérifie parmi les instances existantes si le nom (introduit par l'utilisateur et récupéré par demanderNom) existe déjà
- ajouterElement : ajoute une nouvelle instance de la classe, dont le nom correspond au nom introduit par l'utilisateur et récupéré par demanderNom à la liste des instances existantes
- d'autres fonctions seront sans doute nécessaires
Mais comme ce serait trop facile si je faisais tout le travail à ta place, je vais te laisser réfléchir à la manière de mettre ces fonctions en place par toi-même
Après tout, pourquoi devrais-je faire tout le boulot![]()
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
J'y aie pensémais le coup de la collection ne fonctionne pas avec les cas avec différents types (n'ayant aucun héritage en commun), ni sur l'ajout d'attributs membre de différents types (surtout les PODs), ni avec les collections de différents pointeurs de fonction (au alors peut-être utiliser std::function
)
Par exemple :
- "voiture" -> 4 roues, 1 volant, 4 ou 5 portes - type d'essence, nombre de places occupées (size_t), litre d'essence (float) - conduire, reculer, charger le coffre
- "moto" -> 2 roues, 1 guidon - litre d'essence (float) - piloter
- "tank" -> 2 chenilles, 1 poste de pilotage, 1 tourelle - nombre d'obus (size_t), y_a_t_il_un_guetteur (bool) - avancer, tirer, gérer les obus
Il faut que @Malamba nous renseigne
Oupsss ... Au temps pour moi, j'avais mal interprété le besoin.
Si le but est de pouvoir créer une instance d'une classe à partir du nom de la classe, il faut partir sur le principe d'une fabrique, qui utiliserait une std::map.
Mettons donc que l'on ait une hiérarchie de classes proche deEn faisant au plus simple, nous pourrions creer une fabrique sous une forme proche de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 class Vehicule{ public: Vehicule(Vehicule const &) = delete; Vehicule & operator=(Vehicule const &)= delete; virtual ~Vehicule() = default; std::string const & name() const{ return name_; } protected: Vehicule(std::string const & name):name_{name}{ } private: std::string const name_; }; class Voiture : public Vehicule{ public: Voiture():Vehicule{"voiture"}{ } /* ... */ }; class Camion : public Vehicule{ public: Camion():Vehicule{"camion"}{ } /* ... */ }; class Moto : public Vehicule{ public: Moto():Vehicule{"moto"}{ } /* ... */ };qui nous permettrait, par la suite, de créer différentes instances des différentes classes sous une forme proche de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 class Fabrique{ using VehicleMap = std::map<std::string, std::function<Vehicule * (void)>>; public: /* static*/ Vehicule* creer(std::string const & name){ static VehicleMap vehicles; if(vehicles.empty()) initializeVehicles(vehicles); if(auto const & toCall = vehicles.find(name); toCall!= vehicles.end()){ auto * result = toCall->second(); assert(result!= nullptr && "result is nullpointer"); return result; } throw std::runtime_error("unable to find requested vehicle name"); } private: void initializeVehicles(VehicleMap & v){ v.insert(std::make_pair("voiture", []()->Vehicule*{return new Voiture;})); v.insert(std::make_pair("moto", []()->Vehicule*{return new Moto;})); v.insert(std::make_pair("camion", []()->Vehicule*{return new Camion;})); } };Il va bien sur de soi que les clases Voiture, Moto et Camion sont réduites à leur plus simple expression ici, et qu'il faudrait sans doute veiller à ce que la fabrique renvoie des pointeurs intelligents pour éviter tout problème, mais le principe est néanmoins bien là
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 int main(){ Fabrique fab; auto * first = fab.creer("voiture"); auto * second = fab.creer("moto"); auto * third = fab.creer("camion"); std::cout<<"first is a "<<first->name()<<"\n" <<"second is a "<<second->name()<<"\n" <<"third is a "<<third->name() <<"\n"; }![]()
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
Relis sa phrase"je voudrais que la création de cette classe se fasse à base de son attribut nom" donc la fabrique et l'instance qu'elle crée sont mélangées ... mais les 2 sont créées l'une après l'autre
Ce n'est pas une fabrique normale qui retourne un pointeur (intelligent ou pas) c'est une fabrique qui s'auto fabrique (*)d'où ma première question
(*) Plus précisément, c'est 1 classe qui se crée en 2 fois avec 1 fabrique en 2ième temps
Mais lorsque tu lis sa deuxième réponse "qu'il n'existe aucune classe qu'on ait créé auparavant qui a ce même attribut chaîne de caractère." on comprend que cette chaîne de caractères est une sorte d'identifiant.
Pour garantir l'unicité d'1 identifiant c'est 1 tout autre problème, problème trivial, ... mais cela nécessite de connaître l'énoncé exact![]()
Partager