Partage

Classe de base

16 août 2018 à 23:30:50

Bonjour a tous et à toutes ! Pardonner moi le dérangement, mais je me demandait si il était possible de développer une classe de basse qui manipule les objets qui en hérite. Je m'explique : Admettons que dans un programme, j'ai besoin que chaque objet possède les méthodes copy(), retain(), release() et hash(). Toute ses méthodes sont liés à leurs objets respectifs (copy() par exemple à besoin de savoir quel type d'objet elle doit copier). Et t'il possible d'implanter une classe héritée qui fournisse ses methodes de manière générique ? Et si oui, comment (templates ?) ?

Je remercie tout le monde par avance !

Vous êtes demandeur d'emploi ?
Sans diplôme post-bac ?

Devenez Développeur web junior

Je postule
Formation
en ligne
Financée
à 100%
16 août 2018 à 23:51:37

>une classe de basse qui manipule les objets qui en hérite

Oui, c'est la base.

Si c'est pas possible, c'est qu'il y a une grosse erreur de conception dans vos classes.

(c'est pas les objets qui hérite d'une classe, c'est ses classes filles, elles-mêmes pouvant générer des instances/objets de la dite classe fille)

C'est le principe de substitution de Liskov.

>copy() par exemple à besoin de savoir quel type d'objet elle doit copier

Non, c'est une erreur de conception.

Si l'implémentation de la méthode "copy" doit être différentes dans différentes classes d'une arborescence de classes, vous rendez cette classe virtuelle (elle aurait dû l'être dés la conception si vous avez un minimum conçu votre classe mère). Vous redéfinissez la méthode "copy" dans chacune des classes filles sont l'implémentation de la classe mère ne convient pas. Pas besoin de connaitre le type de l'objet, car il est forcement du type de la classe qui défini la méthode, ou d'une classe fille dont l'implémentation de la méthode reste compatible.

>qui fournisse ses methodes de manière générique ?

Définissez "générique", SVP, avec un cas concret de préférence.

La généricité des Template est principalement lié en terme de flexibilité sur les types, donc pas grand chose à voir avec des classes mère et des classes filles.

Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
17 août 2018 à 14:00:45

Salut et merci de la reponse... « copy() par exemple a besoin de savoir quel type d´objet elle doit copiée. Non, c´est une erreur de conception. » Il faut bien que copy() sache à quels attributs elle à a faire, non ? « Vous redefinissez la méthode copy() dans chacune des classes filles dont l’implementation de la classe mère ne convient pas. » C’est precisement le but de ma question, comment évité de devoir redefinir à chaque fois ?
17 août 2018 à 15:11:52

Bon, je suis assez vindicatif car tes expressions font penser à l'utilisation du RTTI et autre dynamic_cast, et ça c'est pas bien.

On va donc répondre à tes indications et montrer qu'on n'a pas besoin de ces cochonneries, si on fait correctement le travail de conception (sauf cas extrêmement rare, extrêmement rare).

>Il faut bien que copy() sache à quels attributs elle à a faire, non ?

Non, il y a autant d'implémentation de "copy" qu'il y a de contextes nécessaires.

L'astuce, c'est de s'arranger pour qu'il n'y ait pas besoin de beaucoup de contextes différents.

Pour ce qui est de la méthode "copy", en quoi elle se distingue du constructeur de copy ? (constructeur de la forme "MaClass(const MaClass& src)" qui fait une copie bit à bit de l'objet source et qui est automatiquement généré par le compilateur ?

Idem pour "retain" et "release", c'est quoi la sémantique de ces machins par rapport aux services offerts par les smart_pointers ?

Pour hash, c'est quoi sa sémantique ? Il doit être identique si l'objet est le même ou si ses champs son identique ?

Dans le premier cas, "this" ou un tripatouillage de "this" fera l'affaire.

Dans le deuxième cas, vérifiez que la fonction template "std::hash" fait pas déjà le boulot, sinon, une spécialisation par classe qui appelle celle de la classe de base, c'est pas la fin du monde.

Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
17 août 2018 à 16:01:36

Salut,

bacelar a écrit:

Pour ce qui est de la méthode "copy", en quoi elle se distingue du constructeur de copy ? (constructeur de la forme "MaClass(const MaClass& src)" qui fait une copie bit à bit de l'objet source et qui est automatiquement généré par le compilateur ?

Idem pour "retain" et "release", c'est quoi la sémantique de ces machins par rapport aux services offerts par les smart_pointers ?

Et, comme il ne l'a pas signalé, je vais le faire :D

Il faut savoir que les classes qui interviennent dans une hiérarchie de classes ont typiquement une sémantique d'entité : Ce sont, typiquement, des classes pour lesquelles nous voulons systématiquement que chaque instance reste strictement unique lors de l'exécution.

En effet, lorsque l'on écrit un code proche de myObject->truc();, nous voulons avoir la certitude que la fonction truc sera appelée depuis l'instance bien particulière qui est représentée par la donnée myObject, et non d'une éventuelle copie de cette instance.

En effet, tu serais particulièrement emmerdé si ton salaire arrivait sur le compte d'un autre, uniquement parce que vos deux comptes utilisent le même numéro pour s'identifier.  Et tu le serait sans doute encore plus si le loyer de l'autre venait à être débité de ton propre compte pour la même raison ;)

Si bien qu'il n'y a généralement aucun sens à autoriser la création d'une copie sur ce genre de donnée, et qu'il n'y a donc pas plus de sens à envisager la mise en place d'une fonction copy.

Dans certains cas bien particulier (mais je t'avouerai que leur nombre a tendance à se réduire de plus en plus avec le temps :p ) on peut envisager la nécessité de créer de nouvelles instances d'une classe dont les différents états correspondent trait pour trait aux états d'une instance déjà existante, mais dont les éléments qui permettent à chaque instance d'être unique seront différents.

Un tel comportement ne produit pas une copie à proprement parler, mais plutôt ce qu'il conviendrait mieux d'appeler un clone : quelque chose qui est "presque comme l'original", mais qui n'est pas "tout à fait" l'original; ne serait-ce que parce qu'il pourra vivre sa "propre vie".

Mais, du coup, le nom que l'on pourrait donner à ce comportement ne devrait pas être copy (qui implique de créer une "véritable  copie"), mais plus proche de clone, qui correspond bien d'avantage à ce que l'on veut effectivement faire ;)

Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
20 août 2018 à 21:26:44

Merci de vos réponse ! Je vous explique mon problème: je développe une lib en C++. Le truc, c'est que je veux la rendre accessible aux développeurs C, étant un grand fan de ce langage. Cela se fait au moyen d'un wrapper, et dans les headers, au lieu de fournir des classes (qui existent pas en C), j'utilise des structures qui les représentes. Ceux qui on fait du C savent que la gestion de la mémoire est un vrai casse-tête, et en C  les pointeurs intelligents n'existent pas. Je compter donc fournir à chaque "pseudo classes" C des fonctions retain(), release() et autorelease() (jeter un coup d’œil à l'objective-C et à Cocoa si vous voyez pas de quoi je parle). Sauf que comme l'interface avec le C se fait à l'aide d'un simple wrapper, ce sont les classes C++ qui doivent fournir ces fonctions ! Du coup je sais pas trop comment faire, parce que c'est vrai que les pointeurs intelligents, c'est génial...

Je suis pas certain d'être très clair...

-
Edité par TomAnderson1 20 août 2018 à 21:28:08

21 août 2018 à 1:32:40

Ce que tu dois alors sans doute faire, c'est veiller

  • à ce que toutes les ressources manipulée par ta bibliothèques soient des types complètement opaques et
  • à ce que ta bibliothèque soit le passage obligé pour toute manipulation de ces ressources.

Pour t'aider à comprendre, réfléchis un peu à la structure FILE du C:

Quand tu veux manipuler un fichier, tu commence par l'ouvrir avec la fonction fopen, et tu reçois un pointeur de type FILE

Mais, en tant qu'utilisateur de la bibliothèque C, tu n'as absolument aucun moyen (autre que d'aller fouiller dans les fichiers d'implémentation, s'entend) de savoir de quoi cette structure est composée.  C'est ce que l'on appelle un type opaque ;)

En outre, les seules possibilités qui te sont offertes pour manipuler cette structure FILE passent par quelques fonctions bien particulières de la bibliothèque : fread, fwrite, fclose et quelques autres.

De la même manière, ta bibliothèque devrait uniquement exposer des fonctions comme TRUC * giveMeSomeTruc(/* required parameters*/), void doSomething(TRUC *), void doOtherThing(TRUC * /*, autres paramètres*/), etc mais l'utilisateur de ta bibliothèque ne devrait même pas être en mesure de détermier... ce qui compose effectivement cette structure TRUC.

Le meilleur moyen d'arriver à un tel résultat est -- sans doute -- d'aborder la conception des différents éléments d'un point de vue particulier, sur lequel on insiste régulièrement:

Au lieu de réfléchir en priorité aux données qui composent une classe ou une structure, nous conseillons en effet de réfléchir en priorité au services dont tu t'attends à pouvoir profiter lorsque tu la manipule.

Ainsi, si tu veux créer une classe Personne, par exemple, tu t'attends à pouvoir l'interroger sur quelques états particuliers qui la représentent comme

  • "quel est ton nom?"
  • quel est ton prénom?
  • quel age as-tu?
  • Es-tu marié (et, éventuellement : avec qui es tu marié?)
  • Quel est ta profession? ou
  • Où habites tu?

Et que tu t'attends à pouvoir lui donner des ordres bien précis, comme

  • Déménage (à telle adresse)
  • Marie toi (avec telle personne) ou
  • Change de profession (pour telle autre)

(ce ne sont, bien sur, que des exemples :D . )

Et ce n'est qu'une fois que tu as définis les différents services dont tu t'attend à pouvoir disposer grâce à ta structure que tu va commencer à déterminer "quelle donnée permettra à la structure de fournir le résultat attendu"

Bien sur, cela t'amènera sans doute à envisager de fournir "une chaine de caractères" pour représenter le nom, une autre pour représenter le prénom et une troisième pour représenter la profession, ainsi qu'une structure "date" qui te permettra de calculer l'age sur base de "la date courante", une structure de type "adresse" et un "pointeur vers une autre personne"; ce qui reviendra sans doute au même que si tu avais directement réfléchis aux données qui permettent de représenter la notion de "personne".

Mais, comme tu as commencé par réfléchir aux services dont tu veux profiter, la manière d'organiser ces données pourra être considérée comme... de vulgaires détails d'implémentation.

Mieux encore, tu auras sans doute créé ta classe sous une forme proche de

class Personne{
public:
    Personne(std::string const & nom, std::string const & prenom,
             Adresse const & adresse, Date DDN, Personne * epoux = nullptr)
    std::string const & nom() const;
    std::string const & prenom() const;
    bool esTuMarie() const;
    Personne const & epoux() const;
    void MarieToi(Epoux * );
    Adresse const & adresse() const;
    void Demenage(Adresse const &);
    std::string const & profession() const;
    void changeProfession (std::string const &);
     
};

Et l'on se rend compte que toutes les fonctions qui apparaissent dans l'interface publique devront sans doute trouver leur équivalent dans la version C de la bibliothèque, parce que tu voudra sans doute avoir quelque chose comme.

extern "C"{
    struct Adresse;
    Adresse * createAdresse(char * commune,char * rue,
                            int numero, int codepostal);
    void afficheAdresse(Adrese * );

    struct Date;
    Date * createDate( int jour, int mois, int annee)
    struct Personne;
    Personne * createPersonne(char * nom, char * prenom,
                              Adresse * adresse,
                              Date * dateDeNaissance;
                              char * profession,
                              Personne * epoux);
    char * nom(Personne *);
    char * prenom(Personne *);
    int age(Personne *);
    void marier(Personne * , Personne *);
    bool estMarie(Personne *);
    Personne * epoux(Personne *);
    Adresee * adresse(Personne * );
    void demenage(Personne *, Adresse *);
    void destroyPersonne(Personne *);
    /* parce qu'il y a quelque cas où les date et /ou
     * les adresses ne seront pas détruites correctement
     * il faut permettre à l'utilisateur de le faire 
     * lui-même
     */
    void destroyDate(Date * );
    void destroyAddresse(Adresse *);
}

Je te laisse le soin d'implémenter toutes ces fonctions, mais cela ne devrait plus représenter de problème majeur ;)

Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
21 août 2018 à 10:25:26

Heu... Comment dire ? Ta réponse est juste géniale. Super détaillée, super bien expliquer, avec un exemple... Sauf que... Tu m'apprends rien ! C'est déjà comme ça que je compter procéder, d'après les leçons que j'ai tiré quand j'ai voulu implémenter une logique objet en C pur. C'était pas vraiment ça mon problème, mais plutôt de fournir aux pseudo classes C des fonction pour faciliter la gestion de la mémoire, fonctions qui ne sont pas nécessaire en C++ puisqu'il existe les pointeurs intelligents...

PS -> https://www.teddy.ch/c++_library_in_c/, http://www.jonathanbeard.io/tutorials/Mixed_C_C++, vous en pensez quoi ?

21 août 2018 à 12:15:35

On n'est quand même très très loin du problème initial.

>d'un simple wrapper, ce sont les classes C++ qui doivent fournir ces fonctions !

Pourquoi voulez-vous que cela soit un "simple wrapper" ???

Je ne vois pas pourquoi saloper les classes juste pour faire plaisir à l'implémentation d'un bridge pour un langage X ou Y.

Le code qui implémente "retainTypeX(TypeX*)" n'a pas à être un simple passe-plat vers une instance de ClassX.

Ce code peut gérer les objets de type ClassX via des pointeurs intelligents, des compteurs de références etc...

Ce code peut, lui, utiliser des templates pour gérer ces informations internes.

Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
21 août 2018 à 13:51:52

"code qui implémente "retainTypeX(TypeX*)" n'a pas à être un simple passe-plat vers une instance de ClassX.

Ce code peut gérer les objets de type ClassX via des pointeurs intelligents, des compteurs de références etc...

Ce code peut, lui, utiliser des templates pour gérer ces informations internes."

Comment tu utilises les templates et les pointeurs intelligents dans ce code alors qu'il est écrit en C ?

21 août 2018 à 14:02:29

Ce n'est que l'API qui est en C.

"retainTypeX(TypeX*)"est décoré à la C, donc directement appelable depuis du C.

Mais son implémentation, dans me .cpp est en C++.

Ne pas confondre API et implémentation.

Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
21 août 2018 à 15:30:35

Ce que je t'ai expliqué là, c'était -- effectivement -- la partie "simple"...

Après, si tu as, effectivement, des hiérarchies de classes, tu auras sans doute un MachinTrucHolder qui pourra maintenir (en interne!!) les différentes instance de ces classe sous la forme de pointeur intelligents (en C++, donc) mais qui te donnera accès au éléments... sous forme de pointeurs et qui sera sans doute agrémenté de toute  une série de visiteurs, de (systèmes proches des) fabriques, de locators et, qui sait, peut-être même d'un système de signaux et de slots, dont l'implémentation (en C++ !! ) de tes classes extern "C" pourra tirer profit pour fournir les services attendus ;)

Ceci étant dit, on met très certainement "la charrette avant les boeufs" en s'inquiétant de API C (et donc des fonctions extern "C"), car, vu que tu as décidé de coder en C++, le code que tu dois mettre en place dans un premier temps sera... du code C++.

Il sera "toujours bien assez temps" de t'inquiéter de l'API C lorsque ta bibliothèque fonctionnera correctement et aura été correctement testée ... en C++ ;)

Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
21 août 2018 à 17:10:09

"Après, si tu as, effectivement, des hiérarchies de classes, tu auras sans doute un MachinTrucHolder qui pourra maintenir (en interne!!) les différentes instance de ces classe sous la forme de pointeur intelligents (en C++, donc) mais qui te donnera accès au éléments... sous forme de pointeurs et qui sera sans doute agrémenté de toute  une série de visiteurs, de (systèmes proches des) fabriques, de locators et, qui sait, peut-être même d'un système de signaux et de slots, dont l'implémentation (en C++ !! ) de tes classes extern "C" pourra tirer profit pour fournir les services attendus ;)"

J'ai absolument rien compris, t'aurais pas un exemple là pour le coup :)

21 août 2018 à 18:18:08

On part donc du principe que tu auras une hiérarchie de classes proche de
class Base{
public:
   /* pour respecter la sémantique d'entité */
   Base(Base const &) = delete;
   Base & operator=(Base const &) = delete;
   virtual ~Base() = default;
   /* ... */
};

class D1 : public Base{
    /*... */
};
class D2 : public Base{
     /* ... */ 
};
/* ... */
class DN : public Base{
    /* ...*/
};

Comme tu veux pouvoir garder tes ressources, il te faudra sans doute "quelque chose" qui te permette de les maintenir en mémoire et d'y accéder (non, je refuse de parler de "gestionnaire" :D ) qui ressemblera à quelque chose comme

class RessourceHolder{
public:
    /* un tas de fonctions qui te permettent d'ajouter de nouveaux
     * éléments, de retirer ceux qui sont devenus inutiles
     * ou d'accéder aux éléments existants
     */
private:
    std::vector<std::unique_ptr<Base>> items_;
};

(notes d'ailleurs que tu peux choisir autre chose que std::vector selon tes besoins ;) )

En outre, comme tu es sûrement un développeur consciencieux, et que tu sais qu'il faut respecter l'OCP (Open / Close Principle ou "Principe ouvert / fermé"), tu vas mettre en place tout un "système" -- basé sur le polymorphisme et le double dispatch -- qui te permettra d'éviter d'avoir recours au transtypage.

L'une des formes les plus simples à comprendre est sans doute le patron de conception visiteur, qui prendra une forme proche de

class Visitor{
public:
    virtual void visit(D1 /* const */ &) = 0;
    virutal void visit(D2 /* const */ &) = 0;
    /* ... */
    virutal void visit(DN /* const */ &) = 0;
protected:
    /* comme c'est de toutes manière une classe abstraite*/
    Visitor() = default;
    ~Visitor() = default;
    /* mais pour garantir la sémantique d'entité */
    Visitor(Visitor const &) = delete;
    Visitor(Visitor const &) = delete;
};

à partir duquel tu pourras créer des classes dérivées spécifiques aux différents traitement que tu prévois de faire, mais qui nécessitera de modifier ta hiérarchie de classes originale pour lui donner une forme proche de

class Base{
public:
   /* pour respecter la sémantique d'entité */
   Base(Base const &) = delete;
   Base & operator=(Base const &) = delete;
   virtual ~Base() = default;
   virutal void accept(Visitor &) = 0;
   /* ... */
};

class D1 : public Base{
    /*... */
public:
    void accept(Visitor & v) override{
        v.visit(*this);
    }
};
class D2 : public Base{
     /* ... */ 
public:
    void accept(Visitor & v) override{
        v.visit(*this);
    }
};
/* ... */
class DN : public Base{
    /* ...*/
public:
    void accept(Visitor & v) override{
        v.visit(*this);
    }
};

Sans oublier, bien sur, que tu voudras sûrement créer de nouvelles instances de tes classes.  Encore une fois, vu que tu es un développeur consciencieux, tu te tournera peut-être vers quelque chose comme le patron de conception Factory.

Ah oui, mais j'allais oublier: ton RessourceHolder doit pouvoir subsister "en dehors de toute fonction".  Tu pourrais envisager d'en faire une bonne vieille variable globale, mais: "LES VARIABLES GLOBALES C'EST MAL".

Du coup, tu serais peut-être tenté de te tourner vers le patron de conception singleton.  Mais, pas de bol, il s'agit en fait d'un anti-pattern qui pose pas mal de problèmes (je peux te les expliquer au besoin ;) .  Je te conseillerais donc de te tourner plutôt vers le patron de conception Service Locator ;)

Bien sur, je ne connait absolument pas les objectifs de ta bibliothèque, mais, l'un dans l'autre, il se peut parfaitement que tu finisse par avoir l'utilité de la majeure partie des patrons de conception qui existent ;)

Ceci dit, une fois que ta bibliothèque sera au point, tu voudras -- comme tu l'as dit -- la rendre compatible avec le C.  Et, pour y arriver, tu devras mettre en place quelque chose qui ressemble finalement très fort au patron de conception Façade ;)

EDIT : Bien sur, je t'ai présenté une approche particulière, qui peut (ou non) être particulièrement adaptée à ton cas.  Mais il se peut aussi (je ne connait rien de ton projet) qu'elle sot completement inadaptée.

Ton rôle à toi est donc de "rester ouvert" aux différentes possibilités qui s'offrent à toi afin de pouvoir choisir celle qui conviendra le mieux à la situation ;)

-
Edité par koala01 21 août 2018 à 18:20:48

Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait

Classe de base

× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
  • Editeur
  • Markdown