IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

[Architecture] Ajouter des fonctionnalités à une classe


Sujet :

C++

  1. #1
    Membre averti Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Points : 358
    Points
    358
    Par défaut [Architecture] Ajouter des fonctionnalités à une classe
    Bonjour,

    J'aurais une question d'architecture générale sur le C++.
    La question porte sur qu'elle est le meilleur moyen d'ajouter des fonctionnalités à une classe C++.
    Plus précisément, est qu'il existe un moyen de faire une "union" de classes dans le sens de faire une union de leurs méthodes ?

    Je vais illustrer ma question sur un exemple très simple.

    Imaginons que l'on est une classe "Tab" représentant un tableau creux.
    Cette classe contient des structures de données permettant de modéliser l'objet et quelques méthodes permettant de le manipuler.

    Voici une implémentation très naïve (par souci de simplicité) de la classe Tab :

    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
    class Tab {
      public:
        Tab() {
        }
     
        void set(int x, int val) {
         tab.push_back( make_tuple(x, val));
        }
     
        int get() {
         // ...
        }
     
      private:
        vector< pair<int, int> > tab;
    };

    Imaginons que l'on souhaite implémenter une fonctionnalité pour cette classe.
    Par exemple obtenir la moyenne des éléments du tableau.
    Plusieurs possibilités s'offre à nous :

    1> Modifier la classe : on ajoute dans la classe des attribues, et chaque fois qu'on l'on fait un "set", on met a jour ses attributs.
    Cette méthode à plusieurs inconvenants :
    • On alourdit la classe
    • Même si l'on n'utilise pas la fonctionnalité getMoy, des calculs seront effectués chaque fois que l'on fait un "set"



    2> Créer une fonction : on créer une fonction qui prend en argument Tab puis calcule et renvoi la moyenne.
    Cette méthode a plusieurs inconvenants :
    • Les calculs ne se font pas de manière itérative (pas forcément gênant pour cet exemple, mais on peut imaginer des cas ou ceci est un problème)
    • On n'a pas d'attribut lié à l'objet nous permettant des garder certaines informations. Pour cet exemple, on ne sauvegarde pas la valeur de la moyenne, donc chaque fois que l'on fait appelle à getMoy, on doit tout recalculer.



    3> Héritage : on créer une classe TabWithMoy qui ajoute ce dont a besoin pour calculer la moyenne.
    Inconvénients :
    • Très lourd et lent.
    • Si l'on souhaite ajouter plusieurs fonctionnalités, on doit créer une chaine d'héritage (beurk !)



    4> Héritage 2 : Utiliser le design pattern "composite"
    Inconvénients :
    • Lenteurs dues à l'héritage



    5> Algorithme en attribut : on créer une classe qui contient la structure de donné "Tab" et tous les algorithmes dont on a besoin

    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
    class TabWithSomeAlgo {
     Tab tab;
     Moy moy;
     
     void set(int x, int val) {
       tab.set(int x, int val);
       moy.set(int x, int val);
     }
     
     double getMoy() {
       return moy.getMoy();
     }
     
     int get(int x) {
       tab.get(x);
     }
    }
    Cette solution me semble être la meilleure, mais elle a encore quelques défauts.
    Chaque fois que l'on souhaite ajouter une nouvelle fonctionnalité, on doit modifier la classe "TabWithSomeAlgo" à plusieurs endroits :
    • On ajoute un attribut pour la nouvelle fonctionnalité, ET on doit faire appel à ce nouvel attribut dans toutes les méthodes qui sont également présentes dans ce nouvel attribut.




    Existe-t-il une astuce permettant de faire appel à ces méthodes de manière automatique ?
    Un truc du type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef class_union<Tab, Moy> TabWithSomeAlgo;
    De telle sorte que lorsque l'on fait appel à une méthode de TabWithSomeAlgo, cela fasse également appelle à cette même méthode pour tous les objets qui ont également cette méthode.
    Théoriquement, le compilateur a toutes les informations nécessaires pour pouvoir faire ça, mais est-ce que cela est possible à implémenter ?


    Si quelqu'un a une idée ou des remarques là-dessus ?

    Merci

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 059
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 059
    Points : 12 095
    Points
    12 095
    Par défaut
    La solution une ne pose aucun problème.
    Vous ajoutez 2 champs :
    - Un bool IsDirty (initialisé à true)
    - Un int ou le type retourné getMoy. On l'appelera "MoMo".

    Sur appel à la méthode set, vous mettez IsDirty à true.
    Quand vous appelez "getMoy", il vérifie si IsDirty est à false ou à true.
    S'il est à true, vous recalculez la moyenne et vous remplissez le champ "MoMo" avec cette moyenne et vous mettez IsDirty à false.
    Vous renvoyez la valeur de "MoMo".

    Voilà, solution optimale en 5 lignes maximum.

  3. #3
    Membre averti Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Points : 358
    Points
    358
    Par défaut
    Merci pour ta réponse bacelar

    C'est effectivement une solution, mais si on imagine que l'on a beaucoup de fonctionnalités à ajouter, on risque de vite se retrouver avec une très très grosse classe qui risque de devenir illisible.
    Pareille au niveau des introductions de bugs, avec l'augmentation du nombre d'attributs d'une classe (qui augmente avec le nombre de fonctionnalité que l'on implémente), on accroît les chances qu'en ajoutant ou modifiant une fonctionnalité, on se trompe et modifie des attributs destinés à une autre fonctionnalité.
    Dans l'exemple, si MoMo est visible seulement par la "fonctionnalité moyenne", cela réduirait les possibilités de bugs et aiderait à la lisibilité de la classe.

    Pareille si une fonctionnalité à implémenter est compliqué est demande des modifications de plusieurs méthodes.

    En faite, je chercherais à décomposer une classe pour chacune de ces fonctionnalités sans perdre en efficacité.
    Je ne sais pas si ce que je cherche à faire est possible en C++, mais j'ai l’impression qu'une telle architecture permettrait de rendre le code plus simple et lisible tout en facilitant l'ajout, la modification et la suppression de fonctionnalités.
    Vous en pensez quoi ? C'est une fausse bonne idée ? Ça vous semble possible en C++ ?

  4. #4
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 113
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 113
    Points : 32 958
    Points
    32 958
    Billets dans le blog
    4
    Par défaut
    Ce dont parle bacelar est d'ailleurs un simple système de cache et une mise en application classique du mot-clé mutable.

    Sinon, je dirais que tu confonds plusieurs trucs
    - soit ta classe doit fournir ceci et l'implémente
    - soit tu veux faire faire ton truc à ta classe, et dans ce cas il n'y a aucune espèce de raison de modifier la classe et il suffit de créer une fonction externe, possiblement template, pour réaliser ta manip. std::algorithm par exemple.

    Et pour C++17 (je crois), on pourra surcharger une classe avec des fonctions libres et les appeler avec la syntaxe d'appel des fonctions membres, le compilo se chargera de trouver la bonne fonction, membre ou libre, lui-même et d'y passer les paramètres

    Sinon en C# il existe la notion de partial class qui m'a toujours choqué perso.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  5. #5
    Membre averti Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Points : 358
    Points
    358
    Par défaut
    Merci pour ta réponse Bousk

    Citation Envoyé par Bousk Voir le message
    Sinon, je dirais que tu confonds plusieurs trucs
    - soit ta classe doit fournir ceci et l'implémente
    - soit tu veux faire faire ton truc à ta classe, et dans ce cas il n'y a aucune espèce de raison de modifier la classe et il suffit de créer une fonction externe, possiblement template, pour réaliser ta manip. std::algorithm par exemple.
    C'est un peu ça oui, je veux mélanger un peu les deux pour avoir que les avantages sans les inconvenants :p

    soit ta classe doit fournir ceci et l'implémente
    Je garde évidemment ce principe.
    Une classe qui représente la structure de donnée (a la responsabilité de représenter la structure).
    Une classe pour chaque fonctionnalité (uniquement la responsabilité de fournir l'unique fonctionnalité).
    Une dernière classe agrège toutes ces classes (responsabilité unique d'agréger la structure de donnée avec les fonctionnalités que l'on souhaite posséder).

    il suffit de créer une fonction externe, possiblement template, pour réaliser ta manip. std::algorithm par exemple
    Le problème de l'utilisation de fonction externe est qu'on ne peut pas ajouter des attributs à l'objet sur lequel on fait la manipulation (j'ai pris l'exemple de mise en cache dans mon exemple simple, mais ça peut être des données plus complexes).
    L'idée est que lorsque l'on souhaite ajouter une nouvelle fonctionnalité sur un objet, il est parfois nécessaire de lui rajouter des données.

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 113
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 113
    Points : 32 958
    Points
    32 958
    Billets dans le blog
    4
    Par défaut
    Si tu ajoutes des membres, tu altères l'objet; Si tu altères l'objet, c'est un objet différent. L'architecture c'est une vraie chose, c'est pas juste "tiens je vais ajouter un membre 'moyenne' à mon vector pour calculer la moyenne", non.
    Un objet doit avoir un but, une collection sert juste à stocker des objets et ne devrait pas avoir de notion de calcul et mise en cache de quoi que ce soit, sinon ce n'est déjà plus une simple collection.

    Si tu veux juste faire n'importe quoi avec des objets, il existe Python.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  7. #7
    Membre averti Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Points : 358
    Points
    358
    Par défaut
    Désolé, j'ai dû mal m'exprimer, mais oui on dit bien la même chose :
    un objet doit avoir un but simple et unique.
    C'est bien pour cette raison que je cherche à ajouter des fonctionnalités à un objet sans le modifier.

    Je me rends compte que j'aurai dû expliquer mes interrogations par un autre point de vue.
    Je vais essayer une autre approche, qui, je pense, sera plus claire.

    Je reprends de zéro avec l'exemple classique du point coloré.
    On a une classe Point :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Point {  
    public:
     Point(int x, int y);
     int getX();
     int getY();
     void setX(int x);
     void setY(int y);
    private:
     int x;
     int y;
    }
    Si on souhaite un point coloré en vue de faire du polymorphisme : on créer une class PointColore qui va hériter de Point en lui ajourant tout ce qu'il faut pour être un point coloré.


    Si par contre, on souhaite un point coloré, mais pas pour faire du polymorphisme, on n'a plus besoin de passer par de l'héritage (qui est lourd).
    On souhaite juste ajouter une fonctionnalité à une classe existante.
    En l'occurrence, on souhaite ajouter la faculté à Point d'être également coloré.

    Pour faire cela, on ne veut pas modifier la classe Point qui est très bien définie telle qu’elle est.
    La solution classique consiste donc a créer une classe PointColoré qui contient un point et une couleur.
    On peut généraliser cette manière de faire pour créer un point avec un certain nombre de fonctionnalités :

    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
    class PointAvecDesFonctionalite {
    public: 
     
     PointAvecDesFonctionalite(...);
     
     int getX();
     int getY();
     void setX(int x);
     void setY(int y);
     
     void setCouleur(int c);
     int getCouleur();
     
     //...
     
    private:
     Point point;
     Couleur couleur;
     // ...
    };
    Le défaut de cette manière de faire, c'est que lorsque l'on ajoute une fonctionnalité, on doit créer les méthodes présentes dans l'objet représentant la fonctionnalité dans la classe mère.
    Par exemple ici pour ajouter la fonctionnalité de pouvoir être coloré, en plus d'ajouter l'objet "couleur" dans "PointAvecDesFonctionalite", on doit également ajouter les méthodes "getCouleur" et "setCouleur".

    Ce que je cherche à faire (sans savoir si c'est possible en c++) serait que l'ajout des méthodes se fasse de manière automatique.
    Donc, créer la classe "PointAvecDesFonctionalite" avec une sorte d'union de classe qui se définirait par un truc du genre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef class_union<Point, Couleur/*, ...*/> PointAvecDesFonctionalite;

    En faite j'ai deux questions :
    1. Es une fausse bonne idée ? Je ne voie pas d'inconvenant, mais que des avantages à cette manière de faire, es qu'il y a quelque chose que je ne voie pas ?
    2. Es que vous pensez que c'est possible à faire en C++ natif ? Ou alors il faut que je créer un plug-in au niveau du compilateur pour pouvoir ajouter cette fonctionnalité ?


    Tout commentaire est le bienvenu

    Merci

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    Salut,

    De manière générale, l'ISP (Interface Segregation Principle ou principe de la ségrégation des interfaces) devrait t'inciter à garder l'interface de tes classes aussi simples que possibles : toutes les fonctions susceptibles de fournir un résultat sur base des valeurs renvoyées par d'autres fonctions (le plus souvent membre de ta classe) devraient être définies en tant que fonctions libres.

    A partir de là, il n'y a même plus à se poser la question : la possibilité d'obtenir la moyenne des éléments d'une collection n'a besoin que de trois services (tous trois rendus par n'importe quelle collection):
    1. l'accès au premier élément de la collection
    2. l'accès à "ce qui suit le dernier élément" de la collection
    3. et le nombre d'éléments que la collection contient.


    Et, pour calculer la moyenne, nous disposons d'un algorithme (std::accumulate) qui nous permet d'obtenir la somme de tous les éléments.

    Tu n'as donc aucune raison de choisir une autre solution que celle passant par une fonction libre

    De plus, en toute honnêteté, à moins que tu ne doive calculer la moyenne d'un tableau excessivement gros (ce qui poserait très surement des problèmes dans le choix du type des données manipulées, à cause des intervalles susceptibles d'être représentés) et / ou que tu ne doive calculer cette moyenne de manière très répétée sur de très courts intervalles de temps, je ne crois sincèrement pas que tu aies des raison de t'inquiéter des performances que tu obtiendras

    NOTA:
    au lieu d'appeler ta fonction set, je l'appellerais plutôt add, car cela correspond bien d'avantage au service que rend cette fonction

    De même, au lieu d'avoir un get, j'implémenterais sans doute plutôt l'opérateur [] en version constante (et, au besoin, en version non constante) ainsi que les fonction begin,end() (en version constante et, au besoin, en version non constante) et size() qui ne feraient qu'appeler le fonctions équivalentes de la classe std::vector afin de pouvoir disposer des informations utiles à l'utilisation de la classe

    PS : Après, si un bench détaillé démontre qu'un problème de performances est du à l'utilisation d'une fonction libre et qu'un autre bench peut démontrer que l'on gagne vraiment du temps mettre la valeur en cache, alors, il sera peut être intéressant de créer une fonction membre et de mettre en place les mécanismes nécessaires à la mise en cache de la moyenne.

    Mais, de prime abord, sans avoir la preuve que cette approche pose un problème de performance, il vaut mieux assurer la fiabilité et l'évolutivité du système car les gains de performances (si tant est qu'ils soient prouvés) se feront aux dépends de ces deux notions
    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

  9. #9
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 059
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 059
    Points : 12 095
    Points
    12 095
    Par défaut
    Ekinoks, pouvez-vous être plus précis sur votre besoin ?

    Si vous voulez changer le comportement de méthodes et ajouter des champs, le Design Pattern "Decorator" permet de faire cela fonctionnalité par fonctionnalité.
    http://design-patterns.fr/decorateur
    Si vous voulez une façon simple en C++ de faire ces décorateur, regardez du coté du CRTP.
    http://cpp.developpez.com/faq/cpp/?p...ce-que-le-CRTP

    Il me semble que vous avez une vue bien trop centré "liste de champs" pour les classes (structure à la C).
    Les classes en C++, elles n'offrent que des services. Les champs sont des détails d'implémentation.
    Pour qu'un classe ait des classes filles, il faut avoir soigneusement conçu la classe mère pour qu'elle soit dérivable et que les classes filles ne puissent pas endommager les invariants de la classe mère.

    L'objet n'est pas l'alpha et l'oméga du C++.
    Vous pouvez aussi faire autrement qu'en objet votre tambouille.
    Par exemple, pour des mécanismes très dynamiques d'ajout ou de suppression de fonctionnalité, il existe les ECS.

    Es une fausse bonne idée ? Je ne voie pas d'inconvenant, mais que des avantages à cette manière de faire, es qu'il y a quelque chose que je ne voie pas ?
    S'il y a plus que quelques fonctionnalités, une explosion combinatoire du nombre de classe.
    Es que vous pensez que c'est possible à faire en C++ natif ? Ou alors il faut que je créer un plug-in au niveau du compilateur pour pouvoir ajouter cette fonctionnalité ?
    Tout est possible, mais avant de faire une fusée Saturn V, faudrait savoir si un vélo n'est pas suffisant.

  10. #10
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    En fait, je vais même être plus précis par rapport à mon intervention précédente ...

    De manière générale, toute fonctionnalité (j'utilise ce terme à dessein, pour englober aussi bien les différents types utilisateurs que les fonctions ou les variables) n'a de l'intérêt qu'au travers de l'utilisation qui en est faite : si une fonctionnalité n'est pas utile / utilisée, elle n'a simplement pas lieu d'être!

    Ainsi, une classe quelle qu'elle soit n'aura réellement de sens qu'à partir du moment où nous en créerons une instance "quelque part" (dans une classe plus globale ou dans une fonction). Et de même, la possibilité de calculer la moyenne des éléments que contient cette classe et -- pourquoi pas -- la capacité à garder cette valeur en cache afin de ne pas devoir la recalculer à chaque fois cette moyenne ne seront réellement intéressantes que... dans certains cas dans lesquels nous utiliserons une instance de notre classe.

    Il serait donc particulièrement dommage de faire en sorte que la classe trimbale systématiquement avec les données permettant de garder la moyenne en cache alors que ces données seraient, dans une proportion à déterminer, simplement inutiles.

    Dés lors, il n'y a -- à mon sens -- aucune raison de faire en sorte que la fonction capable de calculer la moyenne soit une fonction membre de la classe, et encore moins (forcément ) de raison créer des données membres permettant de la garder en cache.

    Par contre, dans les "quelques" (bon, d'accord, ils peuvent être nombreux ) cas où l'on utilise une instance de notre classe et où l'on a besoin de la moyenne, il n'y a absolument rien qui nous empêche... de rajouter une donnée permettant de garder cette moyenne en cache afin de nous éviter d'avoir à la recalculer à chaque fois.
    Et, si on se rend compte que l'on a souvent besoin de cette capacité à maintenir la moyenne en cache, il n'y a absolument rien qui nous empêche de créer un type particulier qui puisse s'occuper de tout automatiquement.

    Mais, comme l'héritage est la relation la plus forte qui puisse exister entre deux classe, et qu'une classe agissant comme une collection n'a pas forcément sémantique d'entité, une simple composition tout à fait classique me semblerait idéale; quitte à mettre en place permettant de "convertir" ta classe Tab en la classe permettant la mise en cache de la moyenne 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
    33
    34
    35
    36
    class Tab{ // ta classe d'origine
    public:
        usign const_iterator = typename std::vector<Type>::const_iterator;
        Type operator[](size_t index) const;
        const_iterator begin() const;
        const_iterator end() const;
        size_t size() const;
    private:
       std::vector<Type> datas_;
    };
    template <typename ITERATOR>
    Type average( ITERATOR b, ITERATOR e){
        auto total = std::accumulate(b, e, 0);
        return total /std::distance(b,e);
    }
    class TabWithAverage{
    public:
        using const_iterator = typename Tab::const_iterator;
        TabWithAverage(Tab & tab):tab_(tab){
           updateAverage();
        }
        void add(Type toadd){
            tab_.add(toadd);
            updateAverage();
       }
       const_iterator begin() const;
       const_iterator end() const;
       size_t size() const;
    private:
        Tab tab_;
        Type average_;
        void updateAverage(){
            average_ = average(tab_.begin(), tab_.end());
        }
    };
    /* quelques opérateurs de conversion viendraient ici ??? */
    .

    Et, bien sur, j'ai pris l'exemple de la moyenne "par facilité", mais le raisonnement peut être suivi pour n'importe quel algorithme

    Et puis, il y a encore la possibilité d'ajouter un peu de généricité là dedans, avec des traits qui définiront l'algorithme à utiliser, ou que sais-je d'autre
    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

  11. #11
    Membre averti Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Points : 358
    Points
    358
    Par défaut
    Merci d'avoir pris le temps de me lire et de répondre à mes interrogations.

    Je suis désolé, c'est une question un peu théorique d'architecture / langage, mais ça fait pas mal de temps que je me pose la question de savoir pourquoi un tel mécanisme n'est (à ma connaissance) pas utilisé.

    Citation Envoyé par koala01 Voir le message
    De manière générale, toute fonctionnalité (j'utilise ce terme à dessein, pour englober aussi bien les différents types utilisateurs que les fonctions ou les variables) n'a de l'intérêt qu'au travers de l'utilisation qui en est faite : si une fonctionnalité n'est pas utile / utilisée, elle n'a simplement pas lieu d'être!
    C'est un point important avec lequel je suis complètement d'accord.



    Citation Envoyé par koala01 Voir le message
    Ainsi, une classe quelle qu'elle soit n'aura réellement de sens qu'à partir du moment où nous en créerons une instance "quelque part" (dans une classe plus globale ou dans une fonction). Et de même, la possibilité de calculer la moyenne des éléments que contient cette classe et -- pourquoi pas -- la capacité à garder cette valeur en cache afin de ne pas devoir la recalculer à chaque fois cette moyenne ne seront réellement intéressante que... dans certains cas dans lesquels nous utiliserons une instance de notre classe.
    Oui.



    Citation Envoyé par koala01 Voir le message
    Il serait donc particulièrement dommage de faire en sorte que la classe trimbale systématiquement avec les données permettant de garder la moyenne en cache alors que ces données seraient, dans une proportion à déterminer, simplement inutiles.
    Oui, il ne faut absolument pas trimbaler des données dans une classe si elles ne sont jamais utilisées.



    Citation Envoyé par koala01 Voir le message
    Dés lors, il n'y a -- à mon sens -- aucune raison de faire en sorte que la fonction capable de calculer la moyenne soit une fonction membre de la classe, et encore moins (forcément ) de raison créer des données membres permettant de la garder en cache.
    C'est là où je ne suis plus d'accord.
    On s'aperçoit qu'il y a deux cas d'utilisation de la classe :
    - Premier cas : On n'utilisera pas (ou très peu) le calcul de la moyenne.
    Dans ce cas, il n’y a effectivement aucune raison de faire en sorte que la fonction capable de calculer la moyenne soit une fonction membre de la classe.
    - Second cas : On utilisera intensivement le calcul de la moyenne.
    Dans ce cas, il peut être intéressant d'avoir une classe TabWithAverage (comme tu l'as fait) qui contient la classe Tab et la fonctionnalité de pouvoir calculer la moyenne avec un cache.



    Citation Envoyé par koala01 Voir le message
    Par contre, dans les "quelques" (bon, d'accord, ils peuvent être nombreux ) cas où l'on utilise une instance de notre classe et où l'on a besoin de la moyenne, il n'y a absolument rien qui nous empêche... de rajouter une donnée permettant de garder cette moyenne en cache afin de nous éviter d'avoir à la recalculer à chaque fois.
    Oui, c'est une manière classique de procéder, mais je cherche justement a améliorer ça.
    Le reproche que l'on peut faire a cette approche est de devoir écrire soit même la mise en cache.
    Il serait plus intéressant de réutiliser des choses déjà faites (donc une classe qui encapsule Tab et s'occupe de calculer la moyenne avec la mise en cache).



    Citation Envoyé par koala01 Voir le message
    Et, si on se rend compte que l'on a souvent besoin de cette capacité à maintenir la moyenne en cache, il n'y a absolument rien qui nous empêche de créer un type particulier qui puisse s'occuper de tout automatiquement.
    Oui, c'est exactement ce que je souhaite faire, mais de manière plus "automatique" et "modulaire".
    Le but serait de pouvoir créer des types particuliers en composant des classes entre elles.

    Imaginons que l'on est la classe Tab et un certain nombre de fonctionnalités : "Average", "Max", "Min", "Sum", ....
    Suivant les cas d'utilisation, on sait que l'on aura besoin que d'un sous ensemble de fonctionnalité.
    Ça serait très pratique et propre de pouvoir créer un nouveau type particulier en une seule ligne.
    Par exemple si on a besoin d'un Tab avec les fonctionnalités "Average" et "Sum" on écrirait un truc du genre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef class_union<Tab, Average, Sum> SpecifiqueTab;
    De cette manière il est très facile de rajouter ou enlever des fonctionnalités à notre Tab en fonction de notre usage.



    Citation Envoyé par bacelar
    Si vous voulez changer le comportement de méthodes et ajouter des champs, le Design Pattern "Decorator" permet de faire cela fonctionnalité par fonctionnalité.
    http://design-patterns.fr/decorateur
    C'est un peu ce que je souhaite faire, mais avec deux différences :
    - Le pattern décorateur impose de l'on définisse dans la classe de base l'ensemble des méthodes utilisables. La "décoration" définit juste l'implémentation de ces méthodes. Avec mon approche je souhaite "décorer" une classe avec à la foi la déclaration et l'implémentation.
    - Je ne souhaite pas non plus utiliser la lourdeur de l'héritage, car il n'y en a théoriquement pas besoin.

  12. #12
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 627
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 627
    Points : 10 551
    Points
    10 551
    Par défaut
    J'ai l'impression que ce que tu veux c'est faire un tableau une collection de foncteurs amis de la classe Tab et qui prennent en paramètre un objet Tab et qui sont identifiés par un enum

    Ainsi,
    • Tous les paramètres et résultats sont dans les foncteurs, pas dans la classe Tab
    • Avec des push_back et remove tu fais ce que tu veux
    • Tu n'as pas d'héritage, à moins de coder une classe mère pour la mise en cache (ou autre)


    Édit 1:
    • La notion d'amitié c'est pour aller plus vite: on peut faire autrement
    • Les foncteurs ne sont pas la seule solution: koala01 propose le patron strategie et "template method"
    • Les enum ne servent que pour rechercher un élément. Un std::map peut également être une solution.


    Après faire une collection d'objets n'ayant pas une classe mère commune (sans héritage donc) je ne sais pas si c'est possible (puisque l'héritage te fait hésiter grandement )


    Édit 2:
    Le C++ n'est pas de la programmation orientée prototype, pour reprendre les propos de Bousk

  13. #13
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    Citation Envoyé par Ekinoks Voir le message
    C'est là où je ne suis plus d'accord.
    On s'aperçoit qu'il y a deux cas d'utilisation de la classe :
    - Premier cas : On n'utilisera pas (ou très peu) le calcul de la moyenne.
    Dans ce cas, il n’y a effectivement aucune raison de faire en sorte que la fonction capable de calculer la moyenne soit une fonction membre de la classe.
    - Second cas : On utilisera intensivement le calcul de la moyenne.
    Dans ce cas, il peut être intéressant d'avoir une classe TabWithAverage (comme tu l'as fait) qui contient la classe Tab et la fonctionnalité de pouvoir calculer la moyenne avec un cache.
    Je l'ai pas exprimé de la sorte, mais, si tu relis l'ensemble de mon intervention, c'est bel et bien le raisonnement que j'ai suivi
    Oui, c'est une manière classique de procéder, mais je cherche justement a améliorer ça.
    Le reproche que l'on peut faire a cette approche est de devoir écrire soit même la mise en cache.
    Il serait plus intéressant de réutiliser des choses déjà faites (donc une classe qui encapsule Tab et s'occupe de calculer la moyenne avec la mise en cache).
    Et, selon toi, la classe que j'ai nommé TabWithAverage, c'est quoi, si ce n'est une classe qui, justement, encapsule ta classe Tab, qui permet de calculer la moyenne et de maintenir cette donnée en cache
    Oui, c'est exactement ce que je souhaite faire, mais de manière plus "automatique" et "modulaire".
    Le but serait de pouvoir créer des types particuliers en composant des classes entre elles.

    Imaginons que l'on est la classe Tab et un certain nombre de fonctionnalités : "Average", "Max", "Min", "Sum", ....
    Suivant les cas d'utilisation, on sait que l'on aura besoin que d'un sous ensemble de fonctionnalité.
    Ça serait très pratique et propre de pouvoir créer un nouveau type particulier en une seule ligne.
    Par exemple si on a besoin d'un Tab avec les fonctionnalités "Average" et "Sum" on écrirait un truc du genre :
    Bien que je n'ai pas développé ce point, c'est justement ce que j'ai voulu dire par
    Citation Envoyé par moi-même
    Et, bien sur, j'ai pris l'exemple de la moyenne "par facilité", mais le raisonnement peut être suivi pour n'importe quel algorithme

    Et puis, il y a encore la possibilité d'ajouter un peu de généricité là dedans, avec des traits qui définiront l'algorithme à utiliser, ou que sais-je d'autre
    Allez, un petit exemple vite fait
    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    struct averageAlgorithm{
        template <typename ITERATOR>
        Type operator() (ITERATOR b, ITERATOR e) const{
            if(needUpdate){
                auto total = std::accumulate(b, e, 0);
                average = total / std::distance(b,e);
                needUpdate = false;
            }
            return average;
        }
        bool needUpdate{true;}
        Type average;
    };
    struct minAlgorithm{
        template <typename ITERATOR>
        Type operator() (ITERATOR b, ITERATOR e) const{
            if(needUpdate){
                min = *(std::min_element(b,e));
                needUpdate = false;
            }
            return min;
        }
     
        bool needUpdate{true;}
        Type min;
    };
    struct maxAlgorithm{
        template <typename ITERATOR>
        Type operator() (ITERATOR b, ITERATOR e) const{
            if(needUpdate){
                max = *(std::max_element(b,e));
                needUpdate = false;
            }
            return max;
        }
     
        bool needUpdate{true;}
        Type max;
    };
    template <typename ALGO>
    class TabWithAlgo{
     
        using const_iterator = typename Tab::const_iterator;
        TabWithAverage(Tab & tab):tab_(tab){
        }
        void add(Type toadd){
            tab_.add(toadd);
            algo_.needUpdate = true;
       }
       Type compute() const{
           return algo_();
       }
       const_iterator begin() const;
       const_iterator end() const;
       size_t size() const;
    private:
        Tab tab_;
        ALGO algo_;
    }
    Une telle classe pouvant être utilisée sous des formes proches de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int main(){
        Tab t;
        TabWithAlgo<averageAlgo> average(t); // la moyenne
        TabWithAlgo<minAlgo> min(t); // le minimum
        TabWithAlgo<averageAlgo> max(t); // le maximum
    }
    Et, au pire, s'il faut éviter les copies du tableau (ici, il y en aurait déjà trois) afin de permettre aux différentes instances de TabWithAlgo de rester "synchronisées" en termes de données, il reste toujours possible (quoi que peut être délicat) d'utiliser une référence (non constante) pour désigner tab_ et de faire en sorte que le tableau référencé ne puisse être modifié qu'à un seul endroit, qui émettrait un signal auquel pourraient se connecter les différentes instances de TabWithAlgo

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef class_union<Tab, Average, Sum> SpecifiqueTab;
    De cette manière il est très facile de rajouter ou enlever des fonctionnalités à notre Tab en fonction de notre usage.
    Tu n'as -- a priori, même pas besoin d'une union dans le cas présent... le paramètre template fait "tout à ta place" (en plus, les unions peuvent assez facilement foutre le bordel dans un code )
    C'est un peu ce que je souhaite faire, mais avec deux différences :
    - Le pattern décorateur impose de l'on définisse dans la classe de base l'ensemble des méthodes utilisables. La "décoration" définit juste l'implémentation de ces méthodes. Avec mon approche je souhaite "décorer" une classe avec à la foi la déclaration et l'implémentation.
    - Je ne souhaite pas non plus utiliser la lourdeur de l'héritage, car il n'y en a théoriquement pas besoin.
    Dans le cas présent, tu n'as absolument pas besoin du pattern décorateur et, au pire, si tu veux pouvoir maintenir un ensemble d'instance de TabWithAlgo proposant des spécifications différentes, tu as toujours la possibilité de rajouter une classe de base abstraite (qui ne serait pas Tab!!! ) proposant la fonction compute sous la forme d'une fonction virtuelle pure à la classe TabWithAlgo.

    Mais je ne suis que moyennement convaincu de l'utilité de ce stratagème qui -- en outre -- se rapproche bien plus du patron de conception strategie (ou "template method") que du patron de conception décorateur

    Et, de son coté, l'idée d'émettre un signa auquel se connecteraient (plus ou moins automatiquement) les instances de TabWithAlgo, ce ne serait jamais qu'une variation sur le thème... du patron de conception observateur, vu que tu sembles les aimer...

    Mais je ne vais pas m'étendre sur ce sujet, à moins que tu ne m'en fasse la demande
    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

  14. #14
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 113
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 113
    Points : 32 958
    Points
    32 958
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par Ekinoks Voir le message
    C'est là où je ne suis plus d'accord.
    On s'aperçoit qu'il y a deux cas d'utilisation de la classe :
    - Premier cas : On n'utilisera pas (ou très peu) le calcul de la moyenne.
    Dans ce cas, il n’y a effectivement aucune raison de faire en sorte que la fonction capable de calculer la moyenne soit une fonction membre de la classe.
    - Second cas : On utilisera intensivement le calcul de la moyenne.
    Dans ce cas, il peut être intéressant d'avoir une classe TabWithAverage (comme tu l'as fait) qui contient la classe Tab et la fonctionnalité de pouvoir calculer la moyenne avec un cache.
    Et dans ce cas tu fais une classe et tu composes ta collection à l'intérieur, et basta.
    Il n'y a absolument aucun besoin de faire ça générique ailleurs qu'au niveau d'un projet s'il en a besoin.

    Oui, c'est exactement ce que je souhaite faire, mais de manière plus "automatique" et "modulaire".
    Le but serait de pouvoir créer des types particuliers en composant des classes entre elles.
    ça ressemble beaucoup à de la branlette intellectuelle, ça n'a aucun intérêt. Si tu veux t'amuser à l'écrire, fais-toi plaisir, et tu te rendras vite compte, si t'arrives au bout, que c'est un gadget dont tu auras à peine besoin, et les rares fois où tu en as besoin - si tenté que tu l'utilises ne serait-ce qu'une seule fois -, t'aurais perdu 5 fois moins de temps à l'écrire sur le moment qu'à vouloir créer une classQuiPeutToutFaireParParamètrages.

    Imaginons que l'on est la classe Tab et un certain nombre de fonctionnalités : "Average", "Max", "Min", "Sum", ....
    Suivant les cas d'utilisation, on sait que l'on aura besoin que d'un sous ensemble de fonctionnalité.
    Ça serait très pratique et propre de pouvoir créer un nouveau type particulier en une seule ligne.
    Tout juste pratique au niveau du projet. Les projets qui ont utilité de ce truc le mettent en place.
    Et dans l'absolu c'est strictement équivalent à simplement std::map où tu peux passer un foncteur de comparaison des clés, un allocateur, ou n'importe quelle autre collection de la std en fait.

    De cette manière il est très facile de rajouter ou enlever des fonctionnalités à notre Tab en fonction de notre usage.
    Si tu ajoutes ou enlèves une fonctionnalité à une classe, tu changes toute l'utilité et utilisation de la classe. Donc tu changes toute la classe, et une partie du projet qui utilise cette classe. Bref, l'intérêt théorique est au mieux nul en pratique.

    - Le pattern décorateur impose de l'on définisse dans la classe de base l'ensemble des méthodes utilisables. La "décoration" définit juste l'implémentation de ces méthodes. Avec mon approche je souhaite "décorer" une classe avec à la foi la déclaration et l'implémentation.
    - Je ne souhaite pas non plus utiliser la lourdeur de l'héritage, car il n'y en a théoriquement pas besoin.
    Le CRTP n'est pas lourd, c'est défini à la compilation. Et les traits/polices, telles qu'utiliser par la std abondament, ne nécessitent pas de modifier l'interface de la classe sur laquelle elles s'appliquent. Sinon c'est que tu fais mal les choses et à l'envers. Un trait utilise l'interface d'une classe. C'est de la méta-prog plutôt simple à ce seul niveau.

    Sinon j'étais sérieux, met-toi au Python, ou un autre langage de script que je ne connais pas, là tu pourras faire à peu près absolument tout ce que tu veux. Ajoutes donc des fonctions, variables membres, ce que tu veux à ton objet en dynamique, fais tous les checks du monde pour savoir si tel membre variable/fonction existe et utilise-le ou fallback le cas échéant.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  15. #15
    Membre averti Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Points : 358
    Points
    358
    Par défaut
    Citation Envoyé par foetus
    J'ai l'impression que ce que tu veux c'est faire un tableau une collection de foncteurs amis de la classe Tab et qui prennent en paramètre un objet Tab et qui sont identifiés par un enum
    Yes !! J'avais exactement ça en tête !
    Je pensai juste à un truc plus direct pour faire appel aux fonctions plutôt que d'utiliser des enum ou des map.
    Un truc du genre construire à partir de la collection de foncteurs une classe telle que pour chaque foncteur, une méthode est créée en portant le nom de l'identifiant du foncteur.


    Citation Envoyé par koala01
    Allez, un petit exemple vite fait
    Merci pour l'exemple, je comprends mieux.
    Je n’avais pas compris comment tu faisais si tu souhaitais avoir plusieurs fonctionnalités.
    En fait, tu créer un couple (Tab, Algo) pour chaque nouvelle fonctionnalité à utiliser par la suite.
    L'approche est intéressante, j'avais en tête de créer plutôt un seul objet qui encapsule tous les algorithmes pouvant être utilisé par la suite.
    Il faut que je médite sur la différence entre les deux approches...


    Citation Envoyé par koala01
    Tu n'as -- a priori, même pas besoin d'une union dans le cas présent... le paramètre template fait "tout à ta place" (en plus, les unions peuvent assez facilement foutre le bordel dans un code )
    Quand j'écris "class_union", je ne parle pas du mot clé "union" existant en C++, mais plus d'une union au sens mathématique de deux classes.
    Par exemple si on a une classe A :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class A {
       int i{10};  
       void f() {
          cout << i*i << endl;  
       }  
       void g() {    
          i++;  
       }
    }
    Et une classe B :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class B {
       int i{20};  
       int j{20};  
       void f() {    
          cout << i+j << endl;  
       }
    }
    L'union des deux classes serait un truc du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class A_union_B {  
       A a;  
       B b;  
       void f() {    
          a.f();   
          b.f();  
       }  
       void g() {    
          a.g();  
       }  
    };

    Citation Envoyé par Bousk
    ça ressemble beaucoup à de la branlette intellectuelle, ça n'a aucun intérêt.
    Oui, c'est effectivement le risque.
    Mais en y réfléchissant, ce que je raconte repose uniquement sur l'ajout d'un nouvel opérateur "class_union" comme défini ci-dessus.
    Si la création de ce nouvel opérateur peut effectivement s'avérer compliquée, son utilisation par la suite est par contre extrêmement simple (et naturelle ?).

    Citation Envoyé par Bousk
    Si tu ajoutes ou enlèves une fonctionnalité à une classe, tu changes toute l'utilité et utilisation de la classe. Donc tu changes toute la classe, et une partie du projet qui utilise cette classe. Bref, l'intérêt théorique est au mieux nul en pratique.
    Faut voir en pratique, mais si tu souhaites enlever une fonctionnalité, c'est parce qu’elle n'est plus utilisée dans le projet, donc le fait de la retirer ne devait rien casser.
    Si par contre la fonctionnalité est utilisée, il n'y a pas de raison de la retirer.

  16. #16
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 627
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 627
    Points : 10 551
    Points
    10 551
    Par défaut
    Citation Envoyé par Ekinoks Voir le message
    Un truc du genre construire à partir de la collection de foncteurs une classe telle que pour chaque foncteur, une méthode est créée en portant le nom de l'identifiant du foncteur.

    Mais en y réfléchissant, ce que je raconte repose uniquement sur l'ajout d'un nouvel opérateur "class_union" comme défini ci-dessus.

    Faut voir en pratique, mais si tu souhaites enlever une fonctionnalité, c'est parce qu’elle n'est plus utilisée dans le projet, donc le fait de la retirer ne devait rien casser.
    Si par contre la fonctionnalité est utilisée, il n'y a pas de raison de la retirer.
    Oui en fait ce que tu veux faire ce n'est pas [vraiment] de l'objet, ni du C++, ni du code compilé

    Tu rajoutes un moteur de script style Lua ou Javascript ou autre à ton code C++ et avec un gros script qui va gérer l'ajout et la suppression d'un nouveau script (1 script == algo, avec la modification de tes unions)

    Et mettre un système de synchronisation (temps réel, différé ou manuel) pour que ton programme répercute les changements.


    Donc, toute la logique métier en script et le "ciment" en C++ (qui peut devenir vite compliqué avec de la synchronisation de threads, gestion/ observateurs de dossier(s), éventuellement des parsers de fichiers): il faut vraiment avoir une [très] bonne raison

  17. #17
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    J'ai du mal à voir dans quel contexte cela s'utilise.

    Faire du CRTP comme proposé par bacelar est très proche d'une class_union, l'héritage multiple aussi.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template<class... Ts>
    struct class_union : Ts...
    {
      class_union() = default;
      class_union(class_union &&) = default;
      class_union(class_union const &) = default;
      template<class... Args>
      class_union(Args && args)
      : Ts{std::forward<Args>(args)...}
      {}
    };
    Les 2 méthodes ont toutefois le défaut de passer par de l'héritage public.

    Une autre solution consiste à uniformisé les fonctions membres sous un même nom est de passer par des tags ou des récupérations explicites de type.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<class... Ts>
    struct class_union : private Ts...
    {
      //...
      template<class T> T & get() { return static_casr<T&>(*this); }
    };
     
    class_union<Algo1, Algo2> algos;
    auto x = algos.get<Algo1>()/*.funcname*/(/*params...*/);
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    std::tuple<Algo1, Algo2> algos
    auto x = std::get<Algo1>(algos)/*.funcname*/(/*params...*/);
    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
    33
    34
    template<class... Ts>
    struct class_union;
     
    template<class> tid{};
     
    namespace detail {
      template<class T>
      struct item {
        T x;
        auto operator()(tid<T>, /*Args && ... args*/) {
          return x(/*args...*/);
        }
      };
    }
     
    template<class T, class... Ts>
    class class_union<T, T...> : detail::item<T>, class_union<Ts...>
    {
      //...
    public:
      using detail::item<T>::operator();
      using class_union<Ts...>::operator();
    };
     
    template<class T>
    class class_union<T> : detail::item<T>
    {
      //...
    public:
      using detail::item<T>::operator();
    };
     
    class_union<Algo1, Algo2> algos;
    auto x = algos(tid<Algo1>{}/*, params...*/);

  18. #18
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Citation Envoyé par Ekinoks Voir le message
    Si on souhaite un point coloré en vue de faire du polymorphisme : on créer une class PointColore qui va hériter de Point en lui ajourant tout ce qu'il faut pour être un point coloré.
    Le point coloré est un des plus mauvais exemple qui soit pour présenter l'héritage. On ne peut pas respecter à la fois le LSP et le contrat type de la fonction de comparaison (symétrique, réflexif, transitif). C'est impossible. Sur des value objects (comparables donc), on ne peut pas avoir un héritage correct.

    Bref.

    Autant rajouter une mise en cache de la moyenne sur la classe est pour moi une erreur de conception. C'est à l'utilisateur d'être intelligent et à anticiper ce genre de besoins. Autant, offrir une interface uniforme et dynamiquement polymorphique devient très compliqué si on a des matrices/tableaux creux qui se baladent au milieu d'autres.
    Là, à minima pour un polymorphisme statique, cela me fait dire que la moyenne doit être soit implémentée au niveau de la classe (membre ou amie libérée, peu importe), soit à partir d'une paire d'itérateurs sur les valeurs qui saura faire abstraction efficacement des trous, et qui sera compatible avec les itérateurs usuels. Cela sera compliqué car les trous comptent pour 0. Je soupçonne qu'il faille partir sur une fonction dédiée.


    PS: la super union (dont on connait tous les représentants possibles), cela s'appelle boost:variant.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  19. #19
    Membre averti Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Points : 358
    Points
    358
    Par défaut
    Citation Envoyé par foetus
    Tu rajoutes un moteur de script style Lua ou Javascript ou autre à ton code C++ et avec un gros script qui va gérer l'ajout et la suppression d'un nouveau script (1 script == algo, avec la modification de tes unions)
    C'est une idée, mais c'est un peu compliqué d'utilisation, car on doit avoir un script pour chaque algo
    Je pensai plus à un préprocesseur qui traduirait les "union_class" avant la phase de compilation.



    Citation Envoyé par jo_link_noir
    J'ai du mal à voir dans quel contexte cela s'utilise.
    Dès que l'on souhaite agréger plusieurs classes en une seule.
    Un exemple me vient en tête pour le cas d'une architecture Modele Vue Controleur.
    Le Controleur est souvent décomposé en plusieurs sous contrôleurs.
    On se retrouve avec un contrôleur général qui contient un ensemble de sous contrôleur du genre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    class ControleurGeneral {
     ControleurTruc c1;
     ControleurMachin c2;
     ControleurBidule c3;
    public:
     void actionA(Event e) {
        c1.actionA(e);
        c2.actionA(e);
        c3.actionA(e);
     }
     ...
    };
    Une alternative proposée avec l'utilisation de "class_union" est de générer la classe ControleurGenral de la manière suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef class_union<ControleurTruc, ControleurMachin, ControleurBidule> ControleurGenral;
    Les avantages sont :
    - Plus court et simple à comprendre
    - Si on souhaite ajouter un nouveau contrôleur, on a juste à le rajouter dans la class_union
    - Si on souhaite ajouter un type d'action, on n’a rien à faire, il suffit que l'un des sous-contrôleurs contienne la signature de la nouvelle action.
    - Un temps d'exécution rapide : on a pas de listes, d'héritages ni de polymorphisme.


    Citation Envoyé par jo_link_noir
    Faire du CRTP comme proposé par bacelar est très proche d'une class_union, l'héritage multiple aussi.
    Une autre solution consiste à uniformisé les fonctions membres sous un même nom est de passer par des tags ou des récupérations explicites de type.
    C'est proche, mais ce n’est pas exactement ça (ce que je raconte dans l'exemple ci-dessous ne peut pas être fait en utilisant l'une de ces solutions).


    Citation Envoyé par Luc Hermitte
    PS: la super union (dont on connait tous les représentants possibles), cela s'appelle boost:variant.
    Interessant ce boost:variant, je connaissai pas.
    Mais non, ce n'est pas de cette union dont je parle :^/
    Un variant ne peut être assigné qu'a l'un des types lui étant associé à la fois.

    La class_union dont je parle c'est une vraie union mathématique, ou l'objet contient en même temps l'ensemble des attributs et des méthodes de toutes les classes lui étant associé.
    Si on fait la class_union de deux classes, la place mémoire utilisé sera donc la somme de la place mémoire de A et de la place mémoire de B (pas le max des deux).
    Si on fait appelle à une méthode "f", cela fera appelle à la méthode "f" dans A et dans B (si "f" est défini dans les deux classes).

    Il reste quand même une interrogation sur le type à retourner dans le cas ou "A.f" et "B.f" renvoie quelque chose... Surement une "class_union" des deux types retournés pour rester cohérent, mais c'est encore assez floue surtout pour la class_union des types de base...

  20. #20
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 059
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 059
    Points : 12 095
    Points
    12 095
    Par défaut
    Une classe doit être un minimum conçu.
    Là, vos classes, c'est gloubi-boulga de machins et de trucs.
    Cela ne me choque donc pas qu'il n'y ait rien dans les spécifications qui facilite cette tambouille.

    Si c'est vraiment pour faire ce genre de machin, quelques MACRO feraient largement l'affaire.

Discussions similaires

  1. [Débutant] Ajouter des attributs à une classe générée automatiquement par Entity
    Par Pelote2012 dans le forum Entity Framework
    Réponses: 2
    Dernier message: 20/11/2014, 17h07
  2. Réponses: 8
    Dernier message: 21/01/2014, 12h52
  3. Ajouter des méthodes à une classe annotée
    Par ThomasEscolan dans le forum Langage
    Réponses: 2
    Dernier message: 04/09/2012, 18h29
  4. comment ajouter des méthodes à une classe lors Runtime?
    Par revever dans le forum Collection et Stream
    Réponses: 2
    Dernier message: 31/03/2008, 14h53

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo