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

Langage C++ Discussion :

Fonction membre template et polymorphisme


Sujet :

Langage C++

  1. #1
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations forums :
    Inscription : Mai 2007
    Messages : 159
    Points : 119
    Points
    119
    Par défaut Fonction membre template et polymorphisme
    Bonjour,

    J'aurais besoin de pouvoir utiliser le polymorphisme sur une fonction membre template.
    Je sais que c'est interdit, mais y aurait-il une solution de contournement?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class A { 
        virtual template <unsigned _V> auto F() -> int; // Interdit
    }
     
    class B : public A
    {
        template <unsigned _V> auto F() -> int override;
    ];
     
    int main(int, char*[] )
    {
        A *a = new B;
        auto c = a->f<6>();
    }
    Par avance merci de vos réponses

    @+++
    Marc

  2. #2
    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 612
    Points
    30 612
    Par défaut
    Salut,

    Oui, il suffit de créer une fonction virtuelle qui n'est pas template

    D'ailleurs, c'est bien simple : pourquoi voudrais tu créer une fonction template (dont les types requis sont déterminés à la compilation)

    Ceci étant dit, il y a moyen de combiner l'approche générique des template avec l'approche orientée objet des fonctions virtuelles, par exemple, en ayant une classe template qui hérite d'une classe qui ne l'est pas, sous une forme qui serait dés lors 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
    /* une classe de base, non template, ayant sémantique d'entité */
    class Base{
    publlic:
        /* les classes qui intereviennent dans une hiérarchie de classe
         * sont, idéalement, non copiable et non assignables 
         */
        Base(Base const &) = delete;
        Base & operator=(Base const &) = delete;
        virtual ~Base() = default;
        /* une fonciton virtuelle, dont le comportement sera redéfini dans les classes dérivées */
        virtual void foo() /*const */ /* = 0*/;
    };
    /* une classe template, dont les données membres dépendront du paramètre template,
     * qui dérive de la classe de base
     */
    template <typename A/*, typename ... ARGS */>
    class Derived : public Base{
    public:
        void foo() /* const */ override[
            /* ce qui doit être fait */
        }
    private:
        A data_; // la donnée membre nécessaire au service qui doit être rendu par foo
    }
    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

  3. #3
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations forums :
    Inscription : Mai 2007
    Messages : 159
    Points : 119
    Points
    119
    Par défaut
    Bonjour,

    Oui, il suffit de créer une fonction virtuelle qui n'est pas template
    Ca m'aurait étonné

    D'ailleurs, c'est bien simple : pourquoi voudrais tu créer une fonction template (dont les types requis sont déterminés à la compilation)
    La réponse est aussi simple : parce qu'il y a les deux aspects. J'utilise ici les modèles pour faire du paramétrique :
    - J'ai un ensemble de classes ayant un comportement paramétré (avec un entier en l'occurrence), dont le paramètre est connu à la compilation, ET NE PEUT PAS être passé en argument, car alors il n'est plus connu à la compilation. La fonction introduit et/ou participe à des enchainements de fonctions utilisant ce paramètre. Le nombre de valeurs valides du paramètre peut varier au cours du temps (de la vie du programme), mais est toujours connu à la compilation. Cela dit, il m'apparaît comme très compliqué, et impossible à gérer, de créer une fonction par valeur. De plus, j'ai un mécanisme qui provoque l'utilisation de telle ou telle fonction en fonction de la valeur du paramètre.
    - Pendant le cours du programme, à un moment, l'utilisateur doit pouvoir choisir une de ces classes (au travers d'un objet) pour y appliquer une opération 'standard' (la fonction f), donc typiquement du polymorphisme.
    - L'utilisateur ne choisira jamais le paramètre (c'est un paramètre calculé au préalable et qui lui est imposé).
    - La dernière raison est pour moi : je ne savais pas qu'il n'est pas possible de rendre une fonction paramétrique virtuelle... Du coup, j'ai créé tout le réseau de fonctions sans me préoccuper de cet aspect. Ce n'est que quand j'ai implémenté ma classe de base que je suis tombé sur cet os... Du coup, faute de conception... Encore que, je ne vois pas très bien comment j'aurais pu concevoir tout ça autrement.

    Je vais analyser ta proposition, et reviendrai dire comment cela se passe (sans doute lundi ou mardi).
    En tous cas, merci pour ta réponse!

    Cordialement,
    Marc

  4. #4
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations forums :
    Inscription : Mai 2007
    Messages : 159
    Points : 119
    Points
    119
    Par défaut
    Tes idées m'inspirent vraiment! Merci!

    Voici ce qui en est sorti :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    class Base {
        template <unsigned _V> struct F { /*...*/ virtual auto  f(Base &, /* args */) -> ret = 0; }; // classe de base virtuelle pure
    public:
        template <unsigned _V> auto f(/* args */) -> ret { return F().f(*this, /* args */); }
    };
     
    class Derivee {
        template <unsigned _V> struct Fd { /* ... */ auto f(Derivee &cible, /* args */) -> ret { return cible.f<_V>(); }  // Sais plus si ça marche... Ça compile en tous cas.
        template <unsigned _V> struct Fd { /* ... */ auto f(Base &cible,/* args */) -> ret { return dynamic_cast<Derivee &>(cible).f<_V>(); } // Ça semble plus sur.
        template <unsigned _V> auto f(/* args */) -> ret { /* mon action */ }
    };
    Bien entendu, il faut que toutes les classes dérivées implémentent une classe Fd.
    Très dangereux, car sinon insulte à l'exécution du type "Pure virtual fonction called..." : rien n'oblige à la création de la classe dérivée covariante, et donc l'erreur n'apparaîtra qu'en cours d'exécution.
    Un moyen d'éviter ce problème (à part ne pas le faire, ce que je peux comprendre comme conseil)?

    Cordialement,
    Marc

  5. #5
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 069
    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 069
    Points : 12 113
    Points
    12 113
    Par défaut
    ET NE PEUT PAS être passé en argument, car alors il n'est plus connu à la compilation.
    Bin si, "constexpr" est ton ami.
    https://en.cppreference.com/w/cpp/language/constexpr
    donc typiquement du polymorphisme.
    Bin non, la composition est bien plus flexible.
    Pourquoi le Design Pattern Stratégie ne s'adapte pas à votre besoin ?

  6. #6
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations forums :
    Inscription : Mai 2007
    Messages : 159
    Points : 119
    Points
    119
    Par défaut
    Bonjour,

    Tout d'abord oups, faute de carre...
    Dans ma solution je tente d'instancier une classe abstraite. Heureusement, le ridicule ne tue pas...

    @bacelar :
    - constexpr, comme les paramètres template, est incompatible avec virtual (une fonction virtuelle ne peut être constexpr, et constexpr ne peut pas être utilisé sur un argument)
    - Le motif stratégie se rapproche effectivement de ce que je veux faire. Je vais l'étudier de près, sachant que mon problème sera toujours de transmettre mon paramètre pendant la compilation.
    Merci à toi, Bacelar!

    Cordialement,
    Marc
    .

  7. #7
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations forums :
    Inscription : Mai 2007
    Messages : 159
    Points : 119
    Points
    119
    Par défaut
    Bonjour,

    ET NE PEUT PAS être passé en argument, car alors il n'est plus connu à la compilation.
    Bin si, "constexpr" est ton ami.
    Mes excuses bacelar, je me suis mal exprimé : je voulais dire 'car alors SA VALEUR n'est plus analysée à la compilation'. Implication : je complète un peu l'exemple de ma question :
    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
    class A { 
        virtual template <unsigned _V> auto F() -> int; // Interdit
    }
     
    template <unsigned _V> {
        auto k()->int;
    }
     
    class B : public A
    {
        template <unsigned _V> auto F() -> int override { return C<_V>().k(); } // Toujours interdit
        // L'idée est ici de transmettre _V alors que je ne le connais pas à la conception. 
        //Cela dit il sera toujours connu au moment de la compilation (du moins toutes ses valeurs possibles).
        // Et c'est pourquoi _V ne peut être passé en argument.
    };
     
    int main(int, char*[] )
    {
        A *a = new B;
        auto c = a->f<6>();
    }
    Désolé, je ne suis pas le meilleur pour exprimer mes besoins.
    Je pense que je vais devoir passer par un système de fabrique, mais je ne vois pas encore comment, mes réflexions aboutissent toujours à la création d'une fonction virtuelle template...
    Bien sur, toute aide reste la bienvenue

    @ bientôt,
    cordialement,
    Marc

  8. #8
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 069
    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 069
    Points : 12 113
    Points
    12 113
    Par défaut
    je me suis mal exprimé : je voulais dire 'car alors SA VALEUR n'est plus analysée à la compilation'
    Pourquoi ?
    Je suis très dubitatif sur vos raisonnements qui, s'ils ne sont peut-être pas faux, ne sont clairement pas vrai dans le cadre "général".

    Le concept de template est de faire des types l'axe de variabilité du code, l'héritage fait en sorte que l'axe de variabilité du code soit dynamique.
    On n'a donc 2 axes de variabilités antinomiques.

    Et je ne vois toujours pas pourquoi vous voulez absolument coller de la "dynamisité" dans votre bidule.
    Pourquoi avez-vous besoin de machin "virtuel".
    Le cas d'usage de ce bidule est extrêmement restreint et rien dans votre description, parcellaire, ne m'indique un usage pertinent de l'héritage.

    J'ai l'impression que vous nous expliquez pourquoi votre "solution" ne fonctionne pas mais que vous ne nous expliquez pas le "vrai" problème initial.

    Pouvez-vous donner le problème initial, qui vous a mené à cet original "fonction template virtuelle" ?

  9. #9
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations forums :
    Inscription : Mai 2007
    Messages : 159
    Points : 119
    Points
    119
    Par défaut
    Bonjour,

    Je ne vois pas ce qu'il pourrait y avoir d'antinomique entre modèles et polymorphisme? On peut parfaitement avoir besoin des deux, y compris en même temps!

    J'ai un ensemble de classes qui permettent toutes de réaliser une action.
    Cette action est dépendante d'un paramètre dont l'ensemble des valeurs possibles est connu à la compilation. Donc j'utilise des modèles pour paramétrer ces fonctions (Je créée donc autant de fonctions réelles que de valeurs distinctes possibles du paramètre qu'a pu découvrir le compilateur). L'ensemble des valeurs du paramètre peut évoluer au fil du temps.
    Cet ensemble de classes peut également être représenté par une classe parente puisque tous ses éléments permettent de réaliser les mêmes actions. Et donc ont devrait pouvoir prendre un élément quelconque, sans en connaître la classe réelle et dire 'Fais cette action'.
    Le problème c'est juste que la norme actuelle de C++ ne permet pas de représenter cela. Je suppose qu'il y a une forme d'impossibilité car cela implique de générer les fonctions pour toutes les valeurs des paramètres dans toutes les classes dérivées, et que cela représente trop de décisions qui relèveraient plutôt du concepteur).

    Ce que je cherche, c'est de faire cela manuellement.

    Comme application pratique :

    Je dois lire un ensemble de fichiers, qui représentent la même structure de données.
    Ces données sont organisées en enregistrements et en attributs.
    Les fichiers ont une version, c'est à dire que les données ne sont pas forcément représentées de la même manière. Les changements sont assez simples (un type d'enregistrement acquiert ou perd un attribut, ou un type d'attribut peut changer de représentation d'une version à l'autre).

    Jusque là tout va bien, pour réaliser ces lectures, j'ai créé deux ensembles de classes possédant toutes la fonction de lecture : Enregistrement et Attribut
    Pour mettre en place le versioning, j'ai transformé mes fonctions de lecture en modèles, avec comme paramètre la version du fichier lu (je préfère appeler cela paramétrer la fonction). Chaque changement pour une version donnée donne lieu à la spécialisation d'une ou plusieurs fonctions d'une ou plusieurs de mes classes, qui sera valable pour cette version ainsi que les suivantes.
    Note bien que, pour l'instant tout va bien, cette partie est réalisée et fonctionne parfaitement.

    Et maintenant l'os : Pour deux types d'enregistrements, je me trouve en face d'un attribut qui peut prendre des valeurs de n'importe quel type (date, ou bien texte etc...), associé à un sélecteur. Et c'est là que le polymorphisme, tel Zorro, surgit de la nuit (Ok, je sors ). Et la source de ma question.

    Voila. Très compliqué à expliquer convenablement. J'espère que c'est compréhensible.
    Cordialement,
    Marc

  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 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Teaniel Voir le message
    Bonjour,

    Je ne vois pas ce qu'il pourrait y avoir d'antinomique entre modèles et polymorphisme? On peut parfaitement avoir besoin des deux, y compris en même temps!
    Et que penses tu du simple fait qu'il s'agisse en réalité de deux sortes de polymorphismes différentes

    Car, soyons clairs : quand on parle de "polymorphisme", on pense, tout naturellement à la redéfinition de comportement d'une fonction virtuelle, nous sommes bien d'accord

    Mais le fait est que l'on fait un abus de langage honteux en se limitant à cette fonctionnalité, car, la définition du terme polymorphisme est et reste:
    la capacité d'une fonction à adapter son comportement au type de données qu'elle manipule
    Tu remarqueras que cette définition ne parle ni d'héritage, ni de fonctions virtuelles, ni quoi ni qu'est-ce

    Or, il existe quatre possibilités clairement différentes pour obtenir un tel résultat:

    La "plus simple" : la surcharge
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void foo(int i){
        /* bla bla */
    }
    void foo( std::string const & str){
        /* ... */
    }
    void foo(float f ){
        /* ... */
    }
    Voilà qui est un parfait exemple de fonction polymorphique

    La cohercision : c'est le mécanisme qui permet la conversion implicite d'une donnée en une donnée d'un aurte type:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    /* soit ptr est déclaré comme */
    std::unique_ptr<Truc> ptr;
    if(ptr) { // équivalent à if(ptr!= nullptr)
     
    }
    Voila une forme bien surprenante de polymorphisme

    le polymorphisme dit "par inclusion" : c'est la capacité d'une classe dérivée à redéfinir un comportement défini dans sa classe de base:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Base{
    public:
        virtual void foo(){
            std::cout<<"dans Base::foo()\n";
        }
    };
    class Derivee : public Base{
    public:
        void foo() override{    // C++11 et ultérieur inside
            std::cout<<"dans Derivee::foo()\n";
        }
    };
    C'est le polymophrisme auquel on pense généralement "par abus de langage"

    Et enfin le polymorphisme paramétrique: on fournit une spécialisation (partielle ou totale) adaptée à un type template particulier, de manière à ce que le comportement adéquat soit sélectionné à la compilation:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template <typename T>
    struct Test{
    void foo(){
        std::cout<<"ce n'est ni un entier ni un reel\n";
    }
    };
    template <>
    void Test<int>::foo(){
        std::cout<<"C'est un entier\n";
    }
    template <>
    void Test<float>::foo(){
        std::cout<<"C'est un reel\n";
    }
    Tu remarqueras que, quel que soit l'exemple choisi, il y a toujours bel et bien une "adaptation au type de donnée utilisé", et donc "un mécanisme" qu'il convient parfaitement d'appeler polymorphisme.

    Mais tu remarqueras aussi que chaque exemple est strictement adapté à un cas bien spécifique, et que, même si on décide d'avoir recours au double dispatch (comprend : d'avoir recours à un comportement correspondant au polymorphisme d'inclusion pour faire appel à une fonction surchargée), comme par exemple
    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
    class Derivee;
    class Autre;
    class EncoreUneautre;
    void foo(Derivee /* const */ & d){
     
    }
    void foo( Autre /* const */ & a){
     
    }
     
    void foo( EncoreUneautre /* const */ & a){
     
    }
    class Base{
    public:
        virtual void doIt() /* const */ = 0;
    };
    class Derivee :  public Base{
    public:
        void doIt() /* const */ override{
            foo(*this);
        }
    };
    class Autre :  public Base{
    public:
        void doIt() /* const */ override{
            foo(*this);
        }
    };
    class EncoreUneAutre :  public Base{
    public:
        void doIt() /* const */ override{
            foo(*this);
        }
    };
    on ne fait que profiter, à différents moment, de différentes possibilités.

    J'ai un ensemble de classes qui permettent toutes de réaliser une action.
    Hé bien de deux choses l'une:

    Soit ce sont des classes concrètes, "à la sauce POO", qui dérivent toutes d'une classe de base commune, et tu déclares ta fonction virtuelle dans la classe de base et override dans les classes dérivées, et dont le comportement sera sélectionné à l'exécution;

    Soit ce sont des classes "sans rapport les une avec les autres" (en dehors du fait qu'elle exposent une fonction présentant le même prototype), ce qui correspond à la notion de concept, du genre "toute classe exposant telle fonction", et tu en fais une classe template, en fournissant au besoin les spécialisations (partielles ou totales) qui pourraient s'avérer indispensables, et tu la transmet à une fonction template qui choisira à quelle spécialisation faire appel à la compilation.

    Dans certains cas, tu peux éventuellement envisager de fournir une classe de base commune (non template) à une classe template, et déclarer une fonction dans la classe de base (non template) comme étant virtuelle dont le comportement sera redéfini (en la déclarant comme étant override) au niveau de la classe dérivée (template), 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
    class Base{
    public:
        virtual void foo() = 0;
    };
    template<typename T>
    class Derivee : public Base{
    public:
        void foo() override{
            /* ... */
        }
    };
    et pour laquelle tu peux même envisager de fournir une spécialisation (partielle ou totale):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <>
    void Derivee<int>::foo(){
        /* comportement très spécifique */
    }
    Mais il n'y a aucun sens à vouloir déclarer une fonction comme étant virtuelle dans une classe générique "pour la seule raison" que tu envisage de faire dériver une classe non générique de ta classe (pardon : d'une spécialisation totale de ta classe) et de redéfinir le comportement de la fonction en question.

    De toutes manières, deux solutions s'offrent encore à toi:

    Soit la classe template est destinée à apporter une partie de l'interface publique à différentes classe non génériques qui, selon la spécialisation choisie n'ont pas forcément de lien entre elles (hormis, bien sur, le fait d'exposer une fonction portant un nom identique), comme par exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template <typename T>
    class Model{
    public:
        void doIt(T ){
            /* ... n'oublie pas que tu peux fournir une spécialisation pour n'importe quel type mis à la place de T ;) */
        }
    };
     
    class Truc : public Model<int>{
       /* expose une fonction doIt(int) dont le comportement n'a aucune raison d'être redéfini */
    };
    class Machin : public Model<std::string>{
       /* expose une fonction doIt(std::string) dont le comportement n'a pas d'avantage de raison d'être redéfini */
    };
    Soit ta classe générique est un "détail d'implémentation" pour ta classe non générique, et trois nouvelles possibilités s'offrent à toi:

    Si la classe générique n'apporte aucune donnée dont ta classe non générique a besoin pour "son usage personnel", tu peux te "contenter" d'en créer une (ou plusieurs) instances (entièrement spécialisée) au niveau d'une fonction (portentiellement virutelle) de ta classe non générique:
    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
    /* On utilise toujours la même classe modèle */
    template <typename T>
    class Model{
    public:
        void doIt(T ){
            /* ... n'oublie pas que tu peux fournir une spécialisation pour n'importe quel type mis à la place de T ;) */
        }
    };
     
    class Base {
    public:
        virtual void foo(){
            Model<int> detail;
            detail.doIt(3);
            /* ...*/
            /* rien ne t'empeche d'utiliser plusieurs spécialisations ici ;) */
        }
    };
    /* rien n'empêche d'utiliser une spécialisation différente lorsque tu redéfini le comportement */
    class Derivee : public Base{
        void foo() override{
            Model<float> detail;
            detail.doIt(3.14.15);
        }
    };
    ensuite, si la classe générique apporte des données dont ta classe non générique pourrait avoir besoin pour "son usage personnel", l'idéal est de passer par une composition "classique", sous 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
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    /* On utilise toujours la même classe modèle */
    template <typename T>
    class Model{
    public:
        void doIt(T ){
            /* ... n'oublie pas que tu peux fournir une spécialisation pour n'importe quel type mis à la place de T ;) */
        }
    };
     
    class Truc{
    public:
        void foo(){
            detail_.doIt();
        }
    private:
        Model<int> detail_;
    };
    /* et rien n'empêche d'avoir une hiérarchie de classe, exposant un comportement polymorphe l'idéal, si 
     * la classe dérivée doit pouvoir profiter des fonctionnalité de Model est alors de
     * définir une fonction protégée permettant d'appeler les fonctions de Model
     */
    class Base{
    public:
       virtual void foo(){
            /* on utilise (ou non) la fonction protégée */
            callDoIt();
        }
    protected:
         void callDoIt(int i){
             detail_.doIt(i);
         }
    private:
        Model<int> detail_;
    };
    class Derived : public Base{
    public:
        void foo() override{
            /* si besoin on peut faire appel (de manière indirecte) aux fonctionnalités de Model */
            callDoIt(3):
        }
    }
    Enfin, la dernière solution (mais que j'hésite à proposer, à cause des restrictions qu'elle impose) est d'utiliser l'héritage privé au lieu de la composition expliquée plus haut:
    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
    * On utilise toujours la même classe modèle */
    template <typename T>
    class Model{
    public:
        void doIt(T ){
            /* ... n'oublie pas que tu peux fournir une spécialisation pour n'importe quel type mis à la place de T ;) */
        }
    };
    class Truc : private Model<int>{
       // dispose, dans l'accessibilité privée, d'une fonction doIt(int);
    };
    class Machin : private Model<float>{
       // dispose, dans l'accessibilité privée, d'une fonction doIt(float);
    };
    /*Je vais pas répéter encore une fois le code, car tu auras compris que la classe non générique 
     * peut parfaitement servir de classe de base à une hiérarchie de classes ;)
     */
    Si, avec toutes ces possibilités, tu ne trouves pas encore de solution, c'est que tu n'as pas de problème (ou, plus vraisemblablement, que tu as très mal défini le problème que tu veux résoudre)
    Cette action est dépendante d'un paramètre dont l'ensemble des valeurs possibles est connu à la compilation.
    Attention, parle-t-on de valeur, ou de type

    Car, par nature, par défaut, les classes template permettent d'utiliser différents types de données à la compilation.

    En dehors des expressions constantes, les seules valeurs que l'on peut fournir comme paramètre template sont systématiquement des valeurs numériques entières, dont le type peut être
    • un booléen (true ou false)
    • n'importe quel type primitif entier (char, short, int, long, long long dans leur version "signée" ou "non signée")
    • une valeur énumérée, issue d'une énumération ([c]enum[c] ou d'une énumération forte enum class

    Il n'est pas question d'espérer qu'un code proche desoit espéré à la compilation

    De plus, la technique présente un très gros inconvénient, lorsque l'on utilise des valeur "acceptables" : le code binaire n'est généré que pour les valeurs clairement indiquées (ou dont dépendent les valeurs indiquées).

    Cela implique que si, à l'exécution, tu te trouve face à une simple boucle proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    for(int i=0;i<5;++i){
        model(i);
    }
    et que tu n'avais prévu pour le type dont model est issu, que les valeur 1, 2, 3 et 4, tu risque d'avoir un sérieux problème
    Donc j'utilise des modèles pour paramétrer ces fonctions (Je créée donc autant de fonctions réelles que de valeurs distinctes possibles du paramètre qu'a pu découvrir le compilateur). L'ensemble des valeurs du paramètre peut évoluer au fil du temps.
    Cet ensemble de classes peut également être représenté par une classe parente puisque tous ses éléments permettent de réaliser les mêmes actions. Et donc ont devrait pouvoir prendre un élément quelconque, sans en connaître la classe réelle et dire 'Fais cette action'.
    Mais tu peux le faire! le code
    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
    #include <iostream>
    template <int a, int b>
    constexpr int add(){
        return a+b;
    }
    int main()
    {
        std::cout<< add<1,2>()<<"\n"
                 << add<3,4>()<<"\n"
                 << add<5,6>()<<"\n"
                 << add<7,8>()<<"\n"
                 << add<9,10>()<<"\n"
                 << add<11,12>()<<"\n";
     
    }
    compile et s'exécute parfaitement !

    Mais tu es soumis à certaines restrictions (je viens de t'en parler )

    La seule chose c'est qu'il n'y a absolument aucune raison pour que "cette action" soit virtuelle!

    Comme application pratique :
    Je dois lire un ensemble de fichiers, qui représentent la même structure de données.
    Ces données sont organisées en enregistrements et en attributs.
    Les fichiers ont une version, c'est à dire que les données ne sont pas forcément représentées de la même manière. Les changements sont assez simples (un type d'enregistrement acquiert ou perd un attribut, ou un type d'attribut peut changer de représentation d'une version à l'autre).
    Soit tu t'assure que les numéro de version sont des entiers, et tu peux parfaitement avoir un
    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
     
    template <int version>
    std::vector<LaStruct> readFile(std::ifstream & ifs){
        /* on lit la "version 1" */
    }
    /* "Plus tard", on modifie le format du fichier, on passe à la version 2*/
    template <>
    template <int version>
    std::vector<LaStruct> readFile <2>(std::ifstream & ifs){
        /* on lit la "version 1" */
    }
    /* Quinze ou vingt ans après on en est à la version 125 */
    template <>
    template <int version>
    std::vector<LaStruct> readFile <125>(std::ifstream & ifs){
        /* on lit la "version 1" */
    }
    Ca, cela ne posera aucun problème, en dehors du fait qu'il faudra bien ... choisir dynamiquement la version de la lecture en fonction... de la version indiquée dans le fichier

    Un simple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    readFile<num_version>(ifs);
    ne pourra fonctionner que si num_version est connu... à la compilation.

    Or, d'après ce que tu me dis, ce ne sera le cas qu'à ... l'exécution. Comment compte tu contourner ce problème

    Au travers d'un gros switch ... case , sous une forme qui serait 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
    std::ifstream(fichier);
    int num_version;
    ifs>>num_version;
    switch(num_version){
        case 1:
            readFile<1>(ifs);
            break;
        case 2:
            readFile<2>(ifs);
            break;
        case 3:
            readFile<3>(ifs);
            break;
            /* ... */
        case 156:
            readFile<156>(ifs);
            break;
    }
    Heu, tu n'as pas l'impression de te compliquer un peu beaucoup la vie avec ta fonction template

    Car, soyons clair: tu ne coupera pas à ce switch case, qui devra de toutes manières être mis à jour à chaque nouvelle version de ton fichier

    Que penserais tu d'utiliser une bibliothèque externe qui te permettrait de gérer tout cela sans te casser la tête

    Je pense, par exemple, à boost::serialization qui semblerait plus qu'adaptée à ton problème
    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 régulier
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations forums :
    Inscription : Mai 2007
    Messages : 159
    Points : 119
    Points
    119
    Par défaut
    Bonjour,

    Tout d'abord @koala01 merci pour ton intervention. Merci pour vos interventions à tous de toutes manières

    Citation Envoyé par Teaniel Voir le message
    Bonjour,

    Je ne vois pas ce qu'il pourrait y avoir d'antinomique entre modèles et polymorphisme? On peut parfaitement avoir besoin des deux, y compris en même temps!
    Et que penses tu du simple fait qu'il s'agisse en réalité de deux sortes de polymorphismes différentes
    C'est vrai. Et c'est pourquoi, s'ils servent mes intérêts, je ne vois pas pourquoi on ne pourrait pas les trouver ensemble dans mon code...

    Car, soyons clairs : quand on parle de "polymorphisme", on pense, tout naturellement à la redéfinition de comportement d'une fonction virtuelle, nous sommes bien d'accord
    Mais le fait est que l'on fait un abus de langage honteux en se limitant à cette fonctionnalité
    Pas si honteux que cela : étymologiquement, polymorphique se dit d'une entité qui peut prendre plusieurs formes. Et typiquement c'est le cas du pointeur vers la classe de base... Cela dit, je suis d'accord, c'est "un peu" réducteur.

    Pour poursuivre : J'utilise le polymorphisme paramétrique pour gérer la version de mes fichiers, et dans ce cadre il y a effectivement une factory qui me permet de relier la version lue du fichier à la fonction de lecture 'racine', qu'il conviendra de maintenir. Mon objectif dans ce travail est de n'avoir à maintenir QUE cette factory en cas de changement de version, ainsi que les fonctions activement modifiées bien sur (ce qui en l'occurrence se traduit par la création d'une nouvelle fonction pour la version), et que le maximum de décisions et de choix soient prises au moment de la compilation. Cela signifie que le programme doit se comporter comme si j'avais écrit une grosse fonction unique par version. C'est le 'comme si' qui est important ici, bien sur.

    Cette action est dépendante d'un paramètre dont l'ensemble des valeurs possibles est connu à la compilation.
    Attention, parle-t-on de valeur, ou de type
    On parle bien de valeur (je l'ai répété à plusieurs reprises précédemment) et c'est la raison pour laquelle je parlais de fonctions paramétrées.

    on ne fait que profiter, à différents moment, de différentes possibilités.

    J'ai un ensemble de classes qui permettent toutes de réaliser une action.
    Hé bien de deux choses l'une:

    Soit ce sont des classes concrètes, "à la sauce POO", qui dérivent toutes d'une classe de base commune, et tu déclares ta fonction virtuelle dans la classe de base et override dans les classes dérivées, et dont le comportement sera sélectionné à l'exécution;
    Soit ce sont des classes "sans rapport les une avec les autres" (en dehors du fait qu'elle exposent une fonction présentant le même prototype), ce qui correspond à la notion de concept, du genre "toute classe exposant telle fonction", et tu en fais une classe template, en fournissant au besoin les spécialisations (partielles ou totales) qui pourraient s'avérer indispensables, et tu la transmet à une fonction template qui choisira à quelle spécialisation faire appel à la compilation.
    Et c'est bien là le problème : il y a les deux :
    Mes deux ensembles de classes peuvent réaliser plusieurs actions séparées (paramétrées par un entier) Lis<0>, Lis<1>, Lis<2> etc... Chacun de ces indices impliquant la spécialisation d'une ou plusieurs des sous-fonctions membres impliquées dans le processus. De ce fait ils sont bien sur connus et entièrement définis (j'en possède la liste) au moment de la compilation.
    Cependant, comme déjà dit, deux des classes de mon ensemble représentant les enregistrements, se trouvent avoir un attribut composé d'un sélecteur et d'une valeur dont le type n'est pas prévisible au moment de la compilation, mais faisant partie de l'ensemble des types que peuvent prendre les valeurs d'attribut, (du genre Variant des anciennes versions de Visual basic). Et c'est là que je me trouve en face d'un polymorphisme d'inclusion pour l'ensemble des classes d'attribut.

    J'ai beau tourner le problème dans tous les sens, je ne vois pas comment le définir autrement.

    Le faire entièrement en paramétrique est donc impossible.
    Le faire entièrement en inclusif est possible, mais pas performant. J'en ai écrit une version, elle met plusieurs secondes pour charger un fichier de 6Mo, qu'elle passe essentiellement à calculer quelle fonction de lecture qu'elle doit utiliser pour l'objet qu'elle a à lire.
    J'en ai écrit une version incomplète (qui ignore les variants) en paramétrique, elle met sensiblement moins d'une seconde pour lire le même fichier...
    Ecrire une fonction par version de fichier, je ne l'envisage même pas (actuellement, j'en ai une dizaine, et je ne les ai pas toutes).

    Je viens de jeter un oeil à boost::serialization. Vite vu, en effet elle permet de gérer la version.
    Bon, je ne suis pas super fort en anglais, mais ce que j'ai compris est qu'elle est gérée à l'exécution (la version).
    Si j'arrive à résoudre mon problème, mon principe gère les versions à la compilation (sauf bien sur le switch/case de la fonction d'appel).
    Elle implique toujours l'écriture des fonctions E/S pour chaque classe impliquée, ce que j'ai déjà fait pour deux versions.
    Cela dit, je vais également étudier cette solution, qui semble avoir le mérite d'être simple à mettre en oeuvre (ne serait-ce que par curiosité, et pour comparer les perfs).

    Voila, ma question reste entière. Cependant, se forme un début de solution dans ma tête. Je vous l'exposerai si elle fonctionne.
    Encore merci pour le temps et l'intérêt que vous portez à cette question!

    @bientôt
    Marc

  12. #12
    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 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Teaniel Voir le message
    Bonjour,

    Tout d'abord @koala01 merci pour ton intervention. Merci pour vos interventions à tous de toutes manières

    C'est vrai. Et c'est pourquoi, s'ils servent mes intérêts, je ne vois pas pourquoi on ne pourrait pas les trouver ensemble dans mon code...
    Je croyais avoir clairement démontré qu'on pouvait effectivement les faire cohabiter, mais sous certaines conditions

    Pas si honteux que cela : étymologiquement, polymorphique se dit d'une entité qui peut prendre plusieurs formes. Et typiquement c'est le cas du pointeur vers la classe de base... Cela dit, je suis d'accord, c'est "un peu" réducteur.
    Héé non!!! En informatique, le polymorphisme est une propriété "comportementale" (j'ai failli écrire "fonctionnelle", avant de me rendre compte que cela aurait pu prêter à confusion )

    C'est la propriété qui permet à une fonction d'adapter son comportement en fonction des données qu'elle manipule / qui lui sont fournies.

    Et c'est à ne pas confondre avec la substituabilité, qui est la capacité qui nous est donnée de transmettre une instance d'objet de type B à une fonction qui s'attend à recevoir une instance d'objet de type A, et qui est, en outre, le capcité essentielle décrite par le LSP, qui est le principe principe sur lequel se base toute la notion de programmation orientée objet et de l'héritage public (non générique).

    Pour poursuivre : J'utilise le polymorphisme paramétrique pour gérer la version de mes fichiers, et dans ce cadre il y a effectivement une factory qui me permet de relier la version lue du fichier à la fonction de lecture 'racine', qu'il conviendra de maintenir. Mon objectif dans ce travail est de n'avoir à maintenir QUE cette factory en cas de changement de version, ainsi que les fonctions activement modifiées bien sur (ce qui en l'occurrence se traduit par la création d'une nouvelle fonction pour la version),
    Et le SRP, tu en fais quoi tu le jettes aux orties

    Le rôle d'une factory, c'est de sélectionner l'élément qui sera créé sur base des informations qui lui sont données. ET C'EST TOUT!!!.

    Alors, effectivement, tu peux avoir une hiérarchie de classes 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
    class AbstractReader{
    public:
        /* n'oublions pas la sémantique d'entité *
        AbstractReader(AbstractReader const &) = delete;
        AbstractReader & operator = (AbstractReader const &) = delete;
        virtual ~AbstractReader() = default;
        virtual bool read() = 0;
    };
    class ReaderV1 : public AbstractReader{
    public:
        bool read() override;
    };
    class ReaderV2 : public AbstractReader{
    public:
        bool read() override;
    };
    /*... */
    class ReaderVN : public AbstractReader{
    public:
        bool read() override;
    };
    Et tu peux, effectivement, décider d'avoir recours à une factory pour décider quel version créer.

    Mais!!!! Il n'y a rien à faire :

    1- Pour pouvoir réellement profiter de l'héritage, ta classe de base ne peut pas être générique parce que Reader<1> représentera un type différent de Reader<2> et que seule l’existence d'une classe de base non générique te donnera accès à la substituabilité.

    2- Tu le tournes comme tu veux, il faudra bien tenir compte "quelque part" du fait que les données que tu essayes d'extraire de ton fichier doivent correspondre aux données que ton fichier est en mesure de fournir, sur base de sa version...

    Mainteant, comme je te l'ai dit plus tôt, il y a des solutions, mais toutes nécessiteront ad minima l'intervention à deux niveau.

    La possibilité la plus raisonnable se base sur cette phrase que disait l'autre:
    Tous les problèmes en informatique peuvent être résolus en ajoutant un niveau d'indirection... Hormis, bien sur, un nombre trop important d'indirection
    L'idée étant de partir sur une classe générique proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template <int version>
    class ReaderImpl{};
    pour laquelle tu ajouterais une spécialisation à chaque nouvelle version sous 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
    template <>
    class ReaderImpl<1>{
        void read(){
           / ce qu'il faut pour lire la version  1 */
        }
    };
    template <>
    class ReaderImpl<2>{
        void read(){
           / ce qu'il faut pour lire la version  2 */
        }
    };
     
    / *... */
    template <>
    class ReaderImpl<N>{
        bool read(){
           / ce qu'il faut pour lire la version  N */
        }
    };
    et à partir de là, tu pourrais même envisager de créer une classe dérivée qui serait générique, 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
    class AbstractReader{
    public:
        /* n'oublions pas la sémantique d'entité *
        AbstractReader(AbstractReader const &) = delete;
        AbstractReader & operator = (AbstractReader const &) = delete;
        virtual ~AbstractReader() = default;
        virtual bool read() = 0;
    };
    template <int VERSION>
    class Reader : public AbstractReader{
    public:
        bool read() override{
            return ReaderImpl<VERSION>{}::read();
        }
    };
    De telle manière à ce que, une fois que tu as fourni l'implémentation de la fonction read pour une version donnée, il te suffise d'ajouter le case spécifique à cette version dans ta factory qui prendrait dés lors 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
    std::unique_ptr<AbstractREader> create(int version){
        switch(version){
            case 1: return std::make_unique<ReaderImpl<1>>();
            break;
            case 2: return std::make_unique<ReaderImpl<2>>();
            break;
            /* ... */
            case N: return std::make_unique<ReaderImpl<N>>();
            break;
        }
    }
    et que le maximum de décisions et de choix soient prises au moment de la compilation.

    Il y a sans doute moyen de le faire en définissant des traits de politiques(mais je ne connais pas assez la structure de donnée que tu essaye d'extraire pour t'en donner un exemple satisfaisant), ni même pour arriver à me faire une idée cohérente de comment m'y prendre

    Cela signifie que le programme doit se comporter comme si j'avais écrit une grosse fonction unique par version. C'est le 'comme si' qui est important ici, bien sur.
    Nous sommes bien d'accords, mais CE N'EST PAS UNE RAISON pour essayer de rendre une fonction (issue d'une classe générique) virtuelle à tout prix

    On parle bien de valeur (je l'ai répété à plusieurs reprises précédemment) et c'est la raison pour laquelle je parlais de fonctions paramétrées.
    Et, comme je te l'ai dit, tu peux effectivement utiliser un paramètre template de type int (ou n'importe quelle valeur entière), et fournir une spécialisation pour toutes les valeurs susceptibles de convenir (en plus, pourquoi pas, d'une implémentation pour "toutes les valeurs que tu n'aurais pas prises en compte")


    Et c'est bien là le problème : il y a les deux :
    Mes deux ensembles de classes peuvent réaliser plusieurs actions séparées (paramétrées par un entier) Lis<0>, Lis<1>, Lis<2> etc... Chacun de ces indices impliquant la spécialisation d'une ou plusieurs des sous-fonctions membres impliquées dans le processus. De ce fait ils sont bien sur connus et entièrement définis (j'en possède la liste) au moment de la compilation.
    Mais non, ce n'est pas un problème : au lieu de fournir une spécialisation pour la fonction elle-même, tu fournis une spécialisation pour.. la classe dans son entier
    Un code 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
    template <int i>
    class Reader{
    public:
        bool readFile(std::istream & ifs);
    };
     
    template <>
    class Reader<1>{
    public:
        boolReadFile(std::istream & ifs){
            while(ifs){
                A tempA;
                extact(ifs,tempA);
                B tempB ;
                extract(ifs,tempB);
                MaStruct str{telmpA,tempB};
                /* faudra l'ajouter dans une collection, sans doute */
            }
        }
    private:
        /* les fonctions requises */
        extract(std::istream & ifs, A & myA){
            /* extraction et vérification de la donnée de type A */
        }
        extract(std::istream & ifs, B & myB){
            /* extraction et vérification de la donnée de type B */
        }
     
    };
    serait tout à fait correct

    Cependant, comme déjà dit, deux des classes de mon ensemble représentant les enregistrements, se trouvent avoir un attribut composé d'un sélecteur et d'une valeur dont le type n'est pas prévisible au moment de la compilation, mais faisant partie de l'ensemble des types que peuvent prendre les valeurs d'attribut, (du genre Variant des anciennes versions de Visual basic). Et c'est là que je me trouve en face d'un polymorphisme d'inclusion pour l'ensemble des classes d'attribut.
    Je t'avouerai que j'ai un gros problème avec toute cette discussion : elle est beaucoup trop abstraite pour me permettre d'appréhender correctement tes besoins, et, par conséquent de valider correctement ton point de vue de ce qui te semble être la solution à ton problème

    Car, jusqu'à présent, on a parlé "d'une structure", de "A" ou de "B", mais mais tu n'as donné aucune information, ni quant aux types des données données sous-jacentes, ni quant à l'usage /aux usages au(x)quel(s) tu destine ces données.

    Et, du coup, toutes les fibres de mon corps me crient "attention, ces données ont sémantique de valeur", et c'est d'autant plus vrai maintenant que tu as parlé de "variant". La conséquence est sans appel : je me heurte conceptuellement au fait que l'héritage n'a -- pour compréhension que j'en ai à l'heure actuelle -- absolument rien à voir dans l'histoire.

    Pourrais tu -- sans risquer de dévoiler un secret d'état (car je ne voudrais pas t'attirer d'ennuis) -- donner quelques précisions, telles que:
    1. le nom réel du type de donnée que tu veux obtenir
    2. un (ou plusieurs) exemple(s) du contenu des données dont ce type est composé (pour la version 1 et la version 2, par exemple)
    3. l'usage les données extraites seront soumise

    Cela pourrait très certainement débloquer la situation

    J'ai beau tourner le problème dans tous les sens, je ne vois pas comment le définir autrement.

    Le faire entièrement en paramétrique est donc impossible.
    Le faire entièrement en inclusif est possible, mais pas performant. J'en ai écrit une version, elle met plusieurs secondes pour charger un fichier de 6Mo, qu'elle passe essentiellement à calculer quelle fonction de lecture qu'elle doit utiliser pour l'objet qu'elle a à lire.
    Pourrais tu -- toujours sans dévoiler un secret d'état -- nous montrer le code que tu avais mis au point Car, s'il est vrai que la virtualité a tendance à nuire "un tout petit peu" aux performances, je subodore qu'il y a sans doute beaucoup à dire sur la logique que tu as mise en oeuvre
    J'en ai écrit une version incomplète (qui ignore les variants) en paramétrique, elle met sensiblement moins d'une seconde pour lire le même fichier...
    Ecrire une fonction par version de fichier, je ne l'envisage même pas (actuellement, j'en ai une dizaine, et je ne les ai pas toutes).
    Et pourtant, comme je viens de te l'expliquer, tu n'auras pas vraiment le choix, car il faudra bien que tu adapte ta tentative d'extraction des donnée au nombre et au type de données que contient chaque version du fichier
    Encore merci pour le temps et l'intérêt que vous portez à cette question!
    De rien, le défi semble intéressant
    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

  13. #13
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations forums :
    Inscription : Mai 2007
    Messages : 159
    Points : 119
    Points
    119
    Par défaut
    Bonjour,

    @Koala01 : serait-il possible que tu acceptes le bien fondé de ma question sans nouvelle remise en cause stp?
    En effet, tu viens de réanalyser mon application, et de me redonner sensiblement les solutions que j'avais trouvées jusque là (utilisant les template pour porter la version), et que j'ai fait fonctionner, et ce n'était pas ma demande.
    Ce qui montre que je n'ai pas si mal raisonné finalement (on n'est jamais mieux servi que par soi-même ).

    En gros, dans mon fichier j'ai
    - une entête dans laquelle je trouverai son numéro de version.
    - un certain nombre de tables composées de
    - leur entête pour les identifier,
    - Le nombre d'enregistrements de la table,
    - Les enregistrements eux-même composés de
    - un identifiant numérique unique dans toute la base
    - un certain nombre d'attributs (composition dépendante de l'enregistrement) par exemple nom(texte), prenom(texte), Naissance(date)
    - un séparateur.

    Tout cela je sais le lire dans toutes les versions que j'ai codées.
    Le seul problème qui me reste est que j'ai deux enregistrements dont un attribut peut prendre une valeur de n'importe quel type (texte, date, entier...).
    C'est celui-là que je ne sais pas lire.
    Et je pense qu'il implique un polymorphisme inclusif.
    D'où ma question.

    @bientôt
    Marc

  14. #14
    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 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Teaniel Voir le message
    Bonjour,

    @Koala01 : serait-il possible que tu acceptes le bien fondé de ma question sans nouvelle remise en cause stp?
    Non, pas tant que je n'en serai pas moi-même convaincu

    Dis toi bien que, si je le fais, ce n'est pas pour m'amuser! Que, si je le fais, l'expérience que j'ai du C++, du développement et de la conception en générale ainsi que des forums en particulier me poussent à une conclusion sans appel qui est, au choix,
    1. que tu prend une mauvaise décision basée sur un problème mal compris (ou mal exprimé)
    2. que tu essayes désespérément de contourner un problème qui n'est apparu que suite à une mauvaise décision prise à la base

    Dans tous les cas, la solution correcte pour résoudre ton problème passe par le fait de corriger les erreurs en priorité

    De plus, je ne crois pas faire de demande excessive en te demandant de nommer correctement les classes que tu manipule, car "nommer, c'est créer": si je peux déterminer l'usage que l'on prévoit de faire d'une donnée, je suis -- a priori -- capable de me faire une idée relativement précise (même si très parcelaire) de ce qui a des chances de la composer

    En effet, tu viens de réanalyser mon application, et de me redonner sensiblement les solutions que j'avais trouvées jusque là (utilisant les template pour porter la version), et que j'ai fait fonctionner, et ce n'était pas ma demande.
    Ce qui montre que je n'ai pas si mal raisonné finalement (on n'est jamais mieux servi que par soi-même ).
    Quand on voit les informations dont je disposais, il y a peut-être de quoi se dire que je maîtrise suffisamment les différents aspects du développement en C++ que pour savoir à peu près de quoi je parle, non

    Ne fais jamais l'erreur de croire que ceux qui te répondent sur un forum sont de simples "clampins" qui ont tout juste lu deux lignes d'un cours mal fait sur le langage qu'il manipule. Car tu serais sans doute très fortement surpris

    En gros, dans mon fichier j'ai
    - une entête dans laquelle je trouverai son numéro de version.
    - un certain nombre de tables composées de
    - leur entête pour les identifier,
    - Le nombre d'enregistrements de la table,
    - Les enregistrements eux-même composés de
    - un identifiant numérique unique dans toute la base
    - un certain nombre d'attributs (composition dépendante de l'enregistrement) par exemple nom(texte), prenom(texte), Naissance(date)
    - un séparateur.
    Bon, on avance...
    Tout cela je sais le lire dans toutes les versions que j'ai codées.
    Le seul problème qui me reste est que j'ai deux enregistrements dont un attribut peut prendre une valeur de n'importe quel type (texte, date, entier...).
    Ca, tu m'excuseras, mais non...

    Peut-être la représentation de l'attribut en question peut-elle être de différents types (en fonction de la version, par exemple), mais le type l'attribut, une fois que tu l'as extrait du fichier et que tu veux le représenter en mémoire, lui, il sera toujours le même
    C'est celui-là que je ne sais pas lire.
    Et je pense qu'il implique un polymorphisme inclusif.
    Et non!!! Malheureusement, ce n'est pas du polymorphisme d'inclusion dont tu as besoin dans ce cas

    Le polymorphisme d'inclusion, cela s'applique à des classes qui ont sémantique d'entité. Or, qu'il s'agisse d'un texte, d'une date ou d'un entier, ce sont typiquement des données qui ont sémantique de valeur. Adieux héritage, polymorphisme d'inclusion et autres fonctions virtuelles, dans ce cas!!!!

    Plusieurs solutions s'offrent à toi pour arriver à extraire (un texte, une date ou un entier) de ton fichier. Aucune n'a quoi que ce soit à voir avec le polymorphisme d'inclusion, car ton application dépasse ce stade dés le moment où elle définit la version de ton fichier (et qu'elle génère, d'une manière ou d'une autre, l'instance de classe Reader).

    Le problème, c'est que, encore une fois, je suis dans l'incapacité totale de me faire la moindre idée de ce à quoi correspond "cet attribut" dont tu nous parle.

    Malgré toute ma bonne volonté, je ne sais absolument pas déterminer à quoi il va servir. Et je suis donc dans l'incapacité de te proposer une alternative cohérente.

    Pire encore : tel que tu présente les choses, je t'avouerai que j'ai du mal!

    J'ai du mal à comprendre comment un attribut -- dont j'ignore tout -- pourrait être transmis parfois sous forme de texte, parfois sous forme de date, parfois sous d'autres formes. Parce qu'il faut dire ce qui est : ces différentes formes n'ont rien à voir les unes avec les autres.

    Donc, encore une fois, pourrais tu nommer cet attribut, que je puisse au moins me faire une vague idée de l'utilisation auquel tu le destines et, surtout, pour que j'arrive à concilier ce qui me semble pour l'instant inconciliable

    J'ai la vague intuition que la solution que tu cherches passe par l'utilisation de std::any (ou boost::any, si ton compilateur ne supporte pas encore C++17), mais je n'ai -- pour l'instant -- aucun point d'accroche pour arriver à m'en convaincre...
    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

  15. #15
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations forums :
    Inscription : Mai 2007
    Messages : 159
    Points : 119
    Points
    119
    Par défaut
    Bonjour,

    Comment représentes-tu ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class Saisie {
     unsigned Id;
     (Entier | Texte | Nombre | Date | Heure | Temps) Valeur;
    };
    ???
    Perso je ne connais pas d'autre moyen que la suite :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    class TValeur {...};
    class Entier : TValeur {...};
    class Texte : TValeur {...};
    class Nombre : TValeur {...};
    ...
    ou bien 
    template <class Type>
    class TValeurT : TValeur {...} // avec des spécialisations des fonctions en fonction du paramètre Type. (c'est ce que j'ai fait)
     
    class Saisie {
     unsigned Id;
     TValeur *Valeur;
    }
    Par ailleurs, in fine ma question du début peut être résolue en écrivant

    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
     
    class A { 
        template <unsigned _V> auto F() -> int { throw; /* valeur _V invalide */ } // Ou un truc qui génère une erreur à la compilation
        virtual auto F1() -> int=0; 
        virtual auto F2() -> int=0; 
    ...
    }
     
    template auto F<1>() -> int { return F1(); }
    template auto F<2>() -> int { return F1(); }
    ...
     
    class B : public A
    {
        template <unsigned _V> auto F() -> int { throw; /* valeur invalide */ }
        auto F1() -> int=0 override { return F<1>(); } 
        auto F2() -> int=0 override { return F<1>(); } 
    };
     
    int main(int, char*[] )
    {
        A *a = new B;
        auto c = a->f<6>(); 
    }
    C'est une solution qui ne me plait pas car je pensais que le compilateur pourrait faire ce travail à ma place, réduisant ainsi l'effort nécessaire à l'ajout d'une nouvelle version.
    A la réflexion j'ai également compris pourquoi il n'est pas possible de paramétrer une fonction virtuelle : c'est une simple raison technique tenant au fait que le compilateur C++ compile des modules :
    Admettons que mon code du premier post compilerait (juste pour montrer)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    A.cpp
    f() {
     A *test = new B;
     int j = test->F<5>(); //--> à la compilation A::F<5> et B::F<5> doivent être générées.
     ....
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    B.cpp
    g() {
    A *test = new B;
    int j = test<3>(); // --> A la compilation A::F<3> et B::F<3> doivent être générées
    ...
    }
    Résultat échec à l'édition des liens puisque dans les deux modules les classes A et B ne sont pas identiques. Et ça c'est un problème insoluble.

    Ne fais jamais l'erreur de croire que ceux qui te répondent sur un forum sont de simples "clampins" qui ont tout juste lu deux lignes d'un cours mal fait sur le langage qu'il manipule. Car tu serais sans doute très fortement surpris
    Trouve moi UN SEUL endroit qui pourrait te laisser penser cela!!!
    Ce que tu écris là est extrêmement vexant. Il est évident que si je pensais le dixième de ce que tu as écrit là, ce post serait fermé depuis longtemps! Je ne m'amuserais pas à "perdre" une heure voir plus par réponse que j'écris à ce post, si je pensais imbécile mon ou mes interlocuteurs!

    Si j'ai écrit cela c'est qu'effectivement, personnellement j'ai l'impression d'avoir fait le tour de la question. En effet si j'ai effectivement beaucoup appris à te lire, je n'ai pas écrit une seule ligne de code dans mon programme depuis le début de notre discussion, et cela me gène un peu.
    Et aucun des arguments exposés n'a induit le moindre changement dans mon code (preuve en est que tu as retrouvé la solution que j'y avais mise en œuvre).
    Enfin j'ai bien sur conscience de la différence qu'il y a entre un professionnel du C++ qui travaille avec et l'utilise depuis de longues années (et qui a même écrit un livre sur le sujet) et un simple amateur autodidacte qui l'utilise une heure par jour depuis 4 mois après 10 ans d'interruption...
    Cela n'empêche que j'ai mes idées et que je m'y accroche... Je suis comme toi, je n'accepte pas une théorie si je n'en suis pas convaincu, et cela m'empêche de l'appliquer. "Ca ne se fait pas" ne fait pas partie de mon vocabulaire.

    Cordialement,
    Marc

  16. #16
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    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 115
    Points : 32 963
    Points
    32 963
    Billets dans le blog
    4
    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.

  17. #17
    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 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Teaniel Voir le message
    Bonjour,

    Comment représentes-tu ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class Saisie {
     unsigned Id;
     (Entier | Texte | Nombre | Date | Heure | Temps) Valeur;
    };
    Hé bien, je suis désolé, mais cette classe n'a été créée que pour permettre au moteur interne de ta base de données de fonctionner correctement, parce qu'il n'avait aucun moyen de savoir, au moment où il a créé cette classe, quel serait effectivement le type de la donnée qui serait nécessaire.

    Et le type qui a décidé, à un moment donné, d'utiliser cette classe "telle quelle" dans son code ainsi que le DBA qui a permis la création d'un tel champs dans une table sont soit de parfaits imbéciles, soit d'une incompétence crasse!

    Car il n'y a rien à faire : on ne manipule pas du texte comme on manipule une date, une heure, un entier ou un nombre!

    Il faut veiller à ne pas laisser l'arbre nous cacher la forêt: un champs dans une table, ca peut -- effectivement -- être un entier OU du texte OU un nombre OU une date OU une heure OU une estimation de temps, si l'on parle d'un champs ... en général. Mais dés le moment où l'on parle d'un champs bien particulier, dont l'usage est clairement indiqué par un nom explicite -- et cette classe "saisie" avec son champs "valeur" est beaucoup trop "générique" que pour donner la moindre information -- nous nous mettons forcément dans une situation dans laquelle il n'y a plus qu'un seul type réellement adéquat (aller, peut être deux, selon le cas), car une donnée est identifiée par deux choses:
    1. son nom qui indique l'usage auquel on la destine et
    2. son type qui indique
      • la taille nécessaire pour la représentation de l'information en mémoire
      • l'interprétation qu'il faut faire de chaque byte qui la compose

    D'ailleurs, c'est bien simple : je serais particulièrement surpris si tu trouvais le moindre SGBDR un tant soit peu correct qui accepte effectivement que tu indique trois types potentiels pour n'importe quel champs

    ???
    Perso je ne connais pas d'autre moyen que la suite :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    class TValeur {...};
    class Entier : TValeur {...};
    class Texte : TValeur {...};
    class Nombre : TValeur {...};
    ...
    ou bien 
    template <class Type>
    class TValeurT : TValeur {...} // avec des spécialisations des fonctions en fonction du paramètre Type. (c'est ce que j'ai fait)
     
    class Saisie {
     unsigned Id;
     TValeur *Valeur;
    }
    Par ailleurs, in fine ma question du début peut être résolue en écrivant

    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
     
    class A { 
        template <unsigned _V> auto F() -> int { throw; /* valeur _V invalide */ } // Ou un truc qui génère une erreur à la compilation
        virtual auto F1() -> int=0; 
        virtual auto F2() -> int=0; 
    ...
    }
     
    template auto F<1>() -> int { return F1(); }
    template auto F<2>() -> int { return F1(); }
    ...
     
    class B : public A
    {
        template <unsigned _V> auto F() -> int { throw; /* valeur invalide */ }
        auto F1() -> int=0 override { return F<1>(); } 
        auto F2() -> int=0 override { return F<1>(); } 
    };
     
    int main(int, char*[] )
    {
        A *a = new B;
        auto c = a->f<6>(); 
    }
    Non, ce que tu cherches, c'est bel et bien std::any...

    Pas besoin de fonction virtuelle pour travailler avec cela

    Par contre, je n'en démordrai pas : tous les fichiers qui semblent vouloir te fournir plusieurs types différents pour le même champs -- et, par extension, toutes les bases de données (ou à tous le moins les tables correspondantes) à partir desquelles les fichiers sont générés-- doivent être purement et simplement ignorés, pour ne pas dire gentiment foutus à la poubelle!

    Parce qu'il n'y a rien à faire, la structure qui émerge de tes explications est totalement invalide et à ce point bancale que je m'étonne qu'elle n'ait pas encore explosé à la figure des développeurs qui travaillent dessus.

    Maintenant, je comprends aussi qu'il soit impossible de dire "bon, ben, la structure de cette table est bancale, on la vire purement et simplement", surtout si -- comme tes explications semblent le mettre en avant -- elle a déjà été remplie "en dehors de tout bon sens" au fil des ans, mais, avant de vouloir faire quoi que ce soit d'autre, il faut impérativement résoudre ce problème, et trouver une solution cohérente.

    • Soit on vire purement et simplement le champs en question, mais on risque alors de perdre des informations importantes,
    • Soit on crée trois, quatre, cinq tables supplémentaires pour contenir les données des différents types, et on ajoute une clé externe sur chacune de ces tables, de manière à ce qu'il n'y ait qu'une clé externe "valide", indiquant clairement le type de l'information que l'on obtient,
    • Soit... je sais pas, je ne connaît rien du projet sur lequel tu travaille!!!


    Mais ce dont je suis sur, c'est qu'il ne faut en aucun cas laisser trainer les choses sous cette forme! Peu importe depuis quand cette forme a été utilisée, cela a déjà beaucoup trop duré

    C'est une solution qui ne me plait pas car je pensais que le compilateur pourrait faire ce travail à ma place, réduisant ainsi l'effort nécessaire à l'ajout d'une nouvelle version.
    Mais c'est bel et bien ce que je te disais plus haut : tu subit le poids de décisions complètement aberrantes prises au fil du temps

    Ton seul espoir de survie est ... d'arriver à te soulager d'une partie de ce poids en trouvant la solution cohérente (comprends : qui aura une "petite chance" de remettre ta base de données dans un état cohérent) la plus adaptée à ton problème de fonds.

    A la réflexion j'ai également compris pourquoi il n'est pas possible de paramétrer une fonction virtuelle : c'est une simple raison technique tenant au fait que le compilateur C++ compile des modules :
    Il y a plein de raisons techniques à cela... La principale, c'est que F<X> correspond à un type tout à fait différent de F<Y>Une fois que tu as compris cela, tu comprend assez facilement que la virtualité n'a absolument aucun sens

    Si j'ai écrit cela c'est qu'effectivement, personnellement j'ai l'impression d'avoir fait le tour de la question.
    Le problème, en l'occurrence, c'est que tu aurais sans doute mieux fait de ne pas te contenter d'en faire le tour, mais que tu aurais du aller voir en profondeur.

    Car, je le répète: il y a une incohérence conceptuelle majeure dans la situation que tu nous expose en se sens qu'un champs de table de base de données ne peut pas accepter plusieurs types de données "selon l'humeur du moment".

    Tant que tu ne résoudras pas cette incohérence majeure, toutes les solutions que tu pourrait envisager ne pourront être que bancales

    En effet si j'ai effectivement beaucoup appris à te lire,
    Ca fait plaisir à lire
    je n'ai pas écrit une seule ligne de code dans mon programme depuis le début de notre discussion, et cela me gène un peu.
    Je comprends que ce soit vexant, il n'y a aucune raison d'en être gêné...

    Car il est toujours mieux d'écrire dix lignes de code après avoir réfléchi à "toutes les implications possibles" que de foncer tête baissée pour écrire 100 lignes de code sans y avoir réfléchi
    Et aucun des arguments exposés n'a induit le moindre changement dans mon code (preuve en est que tu as retrouvé la solution que j'y avais mise en œuvre).
    Sans doute parce que c'est la solution la plus cohérente en l'état actuel des choses
    Cela n'empêche que j'ai mes idées et que je m'y accroche...
    Il faut donc juste que tu apprenne à "lâcher prise", à admettre parfois que l'erreur est humaine et que tu t'es peut-être trompé "quelque part"
    Je suis comme toi, je n'accepte pas une théorie si je n'en suis pas convaincu, et cela m'empêche de l'appliquer.
    C'est une excellente chose... Tant que cela ne t'empêche pas d'admettre l'évidence

    "Ca ne se fait pas" ne fait pas partie de mon vocabulaire.
    Je suis bien d'accord avec toi

    Mais, parfois, les contraintes techniques nous obligent à trouver des contournements auxquels on ne songe pas forcément d'un premier abord
    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

  18. #18
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France, Moselle (Lorraine)

    Informations forums :
    Inscription : Mai 2007
    Messages : 159
    Points : 119
    Points
    119
    Par défaut
    Bonjour,

    Et le type qui a décidé, à un moment donné, d'utiliser cette classe "telle quelle" dans son code ainsi que le DBA qui a permis la création d'un tel champs dans une table sont soit de parfaits imbéciles, soit d'une incompétence crasse!
    ouille, ça fait mal!!!
    Je suis bien obligé de l'avouer c'était moi...
    Bon, Circonstances atténuantes Votre Honneur!, en 1997, il n'y avait que la POO, le peu de template que j'utilisais faisait planter mon compilateur (je blague pas : crash error 13, sinon erreur interne du compilateur dès que je mettais deux paramètres dans mon modèle), et la STL en était également à ses débuts. Sans compter que je ne pouvais compter sur personne d'autre que moi-même et les bouquins que j'avais achetés (pas de web pour moi à l'époque).
    //*** GRMPFFFFF!!! jes éditeurs HTML c'est vraiment de la m....!!! je viens de perdre une demi heure de texte ****//
    De DBA il n'y en a pas car c'est enregistré dans un fichier (objet de notre discussion en raison des différentes versions qui en ont été émises).
    C'est un choix que j'ai fait en raison de mon manque de maitrise dans les API de l'époque concernant les BDD, et aussi un peu par conviction... Mais ça c'est un sujet qui n'a rien à voir ici.
    De plus la dimension des fichiers ne le justifie pas (tout tient facilement en mémoire).
    Et enfin, je voulais (et je veux toujours) un programme mono-fichier sans installation : l'application ne doit pas être composée d'autre chose que d'un unique exécutable, de son fichier de données et éventuellement d'un fichier de conf.
    L'objectif étant la transportabilité et l'absence totale d'installation. Tout sur une clé usb (au début c'était une disquette).
    Les utilisateurs sont des non informaticiens parfois juste capables de manipuler une souris (ça, c'est un peu moins vrai aujourd'hui, mais reste tout de même un thème objectif).

    Citation Envoyé par Teaniel Voir le message
    Comment représentes-tu ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
        class Saisie {
         unsigned Id;
         (Entier | Texte | Nombre | Date | Heure | Temps) Valeur;
        };
    Hé bien, je suis désolé, mais cette classe n'a été créée que pour permettre au moteur interne de ta base de données de fonctionner correctement, parce qu'il n'avait aucun moyen de savoir, au moment où il a créé cette classe, quel serait effectivement le type de la donnée qui serait nécessaire.
    Ah... Ben là j'ai un vrai problème parce que je suis toujours dans ce cas. En effet, le type des données de cette classe n'est pas prévisible, car au moins en partie décidée par l'utilisateur.
    Petite explication :
    Le programme à l'origine des fichiers est un gestionnaire de compétitions (que je suis en train de refaire).
    grosso modo et vite dit, nous avons donc des compétitions auxquelles participent des licenciés, et qui sont composées d'épreuves.
    Ces épreuves se basent sur des règlements qui définissent les différentes étapes que franchissent les concurrents, qui elles-même induisent des saisies dépendantes (en type et en nombre) de ces règlements.
    Les règlements sont donc des données du programme. Et le type d'une saisie donnée induite par ces règlements également.
    Pour être complet, cela induit également un système de calcul qui manipule ces saisies, qui doit pouvoir être défini par l'utilisateur (arbre syntaxique soit saisi dans des formulaires récursifs (actuel), soit compilé par un analyseur à partir d'une formule textuelle(à venir))

    Dans ce contexte, tu me diras sans doute le contraire, mais je trouve que la manière dont je l'ai implémentée à l'époque n'est pas si mauvaise que cela... Et en tous cas pas illogique.
    D'autant qu'elle a donné lieu à un programme qui a fonctionné et évolué pendant 10 ans (avant que je ne perde ses sources actuels ), et ensuite fonctionné sans mise à jour encore pendant 10 ans, jusqu'à aujourd'hui... Aujourd'hui il y a des machines sur lesquelles il ne démarre plus (cause ancien win32).
    Les vraies difficultés je les ai "parce que" je change de paradigme dans la conception, et cela me donne du mal.

    Par contre, je n'en démordrai pas : tous les fichiers qui semblent vouloir te fournir plusieurs types différents pour le même champs -- et, par extension, toutes les bases de données (ou à tous le moins les tables correspondantes) à partir desquelles les fichiers sont générés-- doivent être purement et simplement ignorés, pour ne pas dire gentiment foutus à la poubelle!

    Parce qu'il n'y a rien à faire, la structure qui émerge de tes explications est totalement invalide et à ce point bancale que je m'étonne qu'elle n'ait pas encore explosé à la figure des développeurs qui travaillent dessus.
    Pardonne moi, mais là il me semble que tu rentres dans le "ça ne se fait pas". Et encore une fois, je n'ai pas d’œil au beurre noir, donc elle ne m'a pas sauté à la figure <je sors>.
    Je sais, je suis lourd, mais pour moi cette structure a fonctionné jusqu'à présent. Cela ne signifie pas que je ne la remettrai pas en cause, mais qu'il y a au moins un cas pour lequel elle a fonctionné, et donc qu'elle risque au moins un peu de pouvoir rendre service (je vais explorer std::any que je ne connaissais pas jusque là, ce qui me permettra peut-être de répondre à ton injonction). C'est une structure qui a comme caractéristique principale de résoudre le choix d'une méthode pendant l'exécution, et non à la compilation. C'est peut-être le seul cas d'usage au monde où elle a été utile, mais en l'occurrence elle l'a été.

    Cela dit, j'ai tout de même décidé au minimum de revoir ma structure de données en vue de la conception de la partie active de mon programme.
    Jusqu'à présent la seule chose qui m'intéressait était de relire les fichiers créés par son ancienne version, partant du principe que, vu son age, la structure de données n'aurait pas de grosse remise en cause (ce en quoi je peux m'être trompé).
    Maintenant, il va falloir que j'aille plus loin (ce n'est pas un mal, ça arrive juste un peu plus tôt que prévu).

    @micalement,
    Marc

  19. #19
    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 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Teaniel Voir le message
    ouille, ça fait mal!!!
    Je suis bien obligé de l'avouer c'était moi...
    Mais au moins, ca te fait réagir

    Et tu te rendras compte, une fois la "blessure d'amour propre" guérie, que "j'ai pas tord"

    Bon, Circonstances atténuantes Votre Honneur!, en 1997, il n'y avait que la POO, le peu de template que j'utilisais faisait planter mon compilateur (je blague pas : crash error 13, sinon erreur interne du compilateur dès que je mettais deux paramètres dans mon modèle), et la STL en était également à ses débuts. Sans compter que je ne pouvais compter sur personne d'autre que moi-même et les bouquins que j'avais achetés (pas de web pour moi à l'époque).
    Oh, mais, tu n'as aucune raison de te justifier

    On apprend plus de ses erreurs que de ce que l'on fait correctement

    Ce qui importe ici, c'est que, quand tu dis dans ton analyse fonctionnelle (ou dans ce qui en tient lieu) -- c'est à dire "hors de tout contexte" -- que
    Un champs est représenté par une donnée (une valeur) qui peut être une date OU un entier OU une heure OU un texte OU ...
    il va bien falloir finir par "contextualiser" cet aspect.

    Car, tôt ou tard, tu vas vouloir mettre en place la notion de "table", dont le principe est simple vu qu'il s'agit de permettre le regroupement d'enregistrements de même type.

    Bien sur, cela nous amène à la notion d'enregistrement dont la définition correspond à
    Une donnée "complexe", composée de un ou plusieurs champs
    et qui nous amène donc à fournir une définition correcte à la notion de champs et qui correspond à
    un champs est une information nommée utilisée pour fournir tout ou partie des informations requises pour la représentation d'un enregistrement
    Et cela nous amène à un problème de taille, car
    nommer, c'est créer
    Chaque champs est donc clairement défini par le nom que l'on va lui donner. Ce nom a véritablement "quelque chose de magique": c'est lui qui te permet de te faire une idée précise de l'usage auquel tu destine la variable. Si tu le nommes "date_de_naissance", tu sauras qu'il sera utilisé pour "un autre usage" que si tu le nommes "quantité" ou "durée"

    Mieux encore : quand tu choisi le nom de ton champs, on en arrive forcément à ... obliger le champs à prendre un type particulier. En effet, si tu nomme ton champs "quantité", tu te doutes bien qu'une donnée de type "date" ou de type "texte" n'aura absolument aucun sens, vu qu'on ne va pas manipuler les dates, les textes et les entiers de la même manière

    Et cela nous emmène à deux constatations supplémentaires:

    1- Si tu décides de changer le type d'un champs, tu es presque obligé de changer le nom du champs pour qu'il continue à correspondre à l'usage auquel on le destine

    2- Toutes tes tables ayant été -- à un moment remplies -- remplies par différents enregistrements, si tu décides de changer le type d'un champs bien particulier (en le renommant ... ou non), il faut t'assurer que tu seras en mesure de convertir toutes les données de maniière à pouvoir les représenter sous leur "nouvelle forme".

    C'est très bien, ca... Mais dis moi: comment on fait pour convertir une quantité en date

    Au final, le terme que l'on aurait peut-être du utiliser dans l'analyse fonctionnelle (ou dans ce qui en tient lieu) au lieu du OU aurait sans doute été SOIT:
    Un champs est représenté par une donnée (une valeur) qui peut être SOIT une date SOIT un entier SOIT une heure SOIT un texte SOIT ...
    Avec comme sous-entendu que l'on peut choisir n'importe lequel de ces types, mais qu'il y aura jamais qu'un, une fois que le choix a été fait, il est ... absolument irrévocable.

    De DBA il n'y en a pas car c'est enregistré dans un fichier (objet de notre discussion en raison des différentes versions qui en ont été émises).
    Disons donc que tu as "fait office de",

    C'est un choix que j'ai fait en raison de mon manque de maitrise dans les API de l'époque concernant les BDD, et aussi un peu par conviction... Mais ça c'est un sujet qui n'a rien à voir ici.

    De plus la dimension des fichiers ne le justifie pas (tout tient facilement en mémoire).
    Bah, tu sais ce qu'on dit :
    peu importe le vin tant qu'on a l'ivresse
    Pour moi, ce qui importe, c'est que tu as mis au point un système qui te permet de sauvegarder différentes informations, de créer des relations entre elles, et de pouvoir les récupérer par la suite.

    Peu importe le nombre de données qui sont ainsi sauvegardées. Peu importe que ce système se base sur des fichiers plats ou sur un système plus lourd et / ou plus complexe : Voilà exactement le genre de services que l'on s'attend de la part d'une "base de données relationnelle". Si bien que ton système -- quel qu'il soit -- entre de facto dans cette catégorie

    Et c'est d'autant plus vrai que tu as pris soin d'organiser les informations que tu voulais enregistrer de manière "cohérente" (ou, à tout le moins, tu as fait tout ton possible pour y arriver )


    Et enfin, je voulais (et je veux toujours) un programme mono-fichier sans installation : l'application ne doit pas être composée d'autre chose que d'un unique exécutable, de son fichier de données et éventuellement d'un fichier de conf.
    As-tu déjà entendu parler de Sqlite

    Pour faire simple, c'est une bibliothèque qui fourni un moteur SQL complet (ou presque) self contained.

    Autrement dit, tout ce dont tu as besoin, c'est un fichier contenant les données de Sqlite, car le moteur qui permet "d'attaquer" la base de données est directement intégré dans l'exécutable.

    C'est le moteur qui est --entre autres -- utilisé par thunderbird et par firefox lorsqu'il s'agit de sauvegarder des données et de permettre leur accès rapide

    Quitte à refaire une bonne partie, pourquoi n'envisagerais-tu pas de migrer sur un système similaire

    De plus, je présumes que tu envisages -- à termes -- d'ajouter une interface graphique à tout le bastringue... Pour ce faire, je te recommanderais l'usage de la bibliothèque Qt qui -- la vie est quand même bien faite, non -- fournit justement un moteur SQL qui sait manipuler ce type de base de données

    L'objectif étant la transportabilité et l'absence totale d'installation. Tout sur une clé usb (au début c'était une disquette).
    Et je te comprends parfaitement.

    Mais le fait est que tu as essayé de recréer la roue, et que le résultat actuel est une roue ... relativement carre

    Il se peut que tu aies beaucoup plus facile à arrondir ta roue en décidant de faire "table rase du passé" et en décidant de partir avec "les bons outils"


    Ah... Ben là j'ai un vrai problème parce que je suis toujours dans ce cas. En effet, le type des données de cette classe n'est pas prévisible, car au moins en partie décidée par l'utilisateur.
    C'est là que tu te plantes : quand tu es au niveau du fichier, le type de donnée a été défini depuis longtemps, et l'utilisateur (du fichier) n'a pas d'autre choix que de manipuler les données que le fichier lui fourni dans un type bien particulier

    Dis toi bien que, une fois que tu "contextualise" la notion de table (ou d'enregistrement ou de champs), les dés sont jetés:

    Des le moment où tu te dis
    je veux sauvegarder la liste des <vas savoir quoi> pour pouvoir y accéder par la suite
    tu te trouves face à l'obligation de définir une notion concrète:

    Si tu remplace <va savoir quoi> par "participants" -- par exemple -- cette notion va obligatoirement impliquer la présence
    • d'un champs "nom" (de type TEXTE)
    • d'un champs "prenom" (de type TEXTE)
    • (éventuellement) d'un champs "date de naissance" (de type ... DATE)
    • Peut-être d'un champs "numéro d'enregistrement" à la fédération (d'un type... adapté à la représentation de ce numéro)
    • sans doute de plein d'autres champs auxquels je ne pense pas forcément

    Parce que cela représente ... les données dont tu as besoin pour pouvoir manipuler ta notion de "participants".

    Si tu remplaces <va savoir quoi> par "match" pour pouvoir maintenir les scores de deux participants qui se sont affrontés, les données porteront des noms différents et... seront de type différents.

    Et, au final, pour chaque table que tu pourras envisager de sauvegarder, tu te retrouveras à créer... une notion bien particulière qui regroupera un "certain nombre" de champs, dont le nom indique clairement l'usage qui en est fait et dont le type correspond forcément à l'usage et aux manipulations que tu envisage pour chaque champs particulier.


    grosso modo et vite dit, nous avons donc des compétitions auxquelles participent des licenciés, et qui sont composées d'épreuves.
    Hé bien, chaque épreuve devrait avoir sa propre table, permettant de sauvegarder les résultats de chaque licencié y ayant participé, et, surtout, de relier ces résultats au licencié en question

    Ces épreuves se basent sur des règlements qui définissent les différentes étapes que franchissent les concurrents, qui elles-même induisent des saisies dépendantes (en type et en nombre) de ces règlements.
    Et c'est justement pour cela que chaque épreuve devrait être sauvegardée dans une table spécifique: pour permettre à chaque épreuve de définir sa propre "notion d'épreuve" qui lui est bien spécifique, et donc le nombre et le type de chaque champs dont elle a besoin pour créer un enregistrement

    Les règlements sont donc des données du programme. Et le type d'une saisie donnée induite par ces règlements également.
    Non!!! le règlement, il est défini ... ailleurs... Tu ne fais que fournir "une certaine vue", une "certaine manière de représenter" ce règlement au travers de ton fichier. Mais tout ce que cela peut comporter a déjà été défini!!! Tout ce que tu veux, c'est... pouvoir le récupérer de manière cohérente

    Pour être complet, cela induit également un système de calcul qui manipule ces saisies, qui doit pouvoir être défini par l'utilisateur (arbre syntaxique soit saisi dans des formulaires récursifs (actuel), soit compilé par un analyseur à partir d'une formule textuelle(à venir))
    Mais ca, c'est un autre problème... qui ne nous concernera qu'une fois que l'on aura la certitude que les données peuvent être récupérées

    Dans ce contexte, tu me diras sans doute le contraire, mais je trouve que la manière dont je l'ai implémentée à l'époque n'est pas si mauvaise que cela... Et en tous cas pas illogique.
    Bien sur que je dis le contraire, car il suffit de te poser une question: comment veux tu faire pour déterminer l'usage auquel tu destine une information dont le type peu varier, et qui n'est pas nommée

    Comme je te l'ai dit et répété : un type permet de savoir quelles manipulations sont possibles alors qu'un nom permet de savoir à quel usage la donnée est destinée.

    Tu peux "assez facilement" déduire l'un quand tu connaît l'autre, sans courrir "trop de risques" de te tromper. Mais, si tu n'a ni l'un ni l'autre, tu es purement et simplement bloqué...

    Le seul espoir qu'il te reste passe par ... la force brute: tu dresses la liste exhaustive des types que tu es susceptible d'obtenir et tu extrait la donnée sous une forme "générique" (une chaine de caractères), et tu vérifies le format de cette chaîne de caractères pour en déterminer le type effectif (en espérant que chaque type puisse être défini par un format spécifique).

    Sauf que cette approche va rapidement tourner au cauchemard

    D'autant qu'elle a donné lieu à un programme qui a fonctionné et évolué pendant 10 ans (avant que je ne perde ses sources actuels ), et ensuite fonctionné sans mise à jour encore pendant 10 ans, jusqu'à aujourd'hui... Aujourd'hui il y a des machines sur lesquelles il ne démarre plus (cause ancien win32).
    Les vraies difficultés je les ai "parce que" je change de paradigme dans la conception, et cela me donne du mal.
    Pourquoi crois tu que j'insiste pour que tu revoie l'ensemble de la conception

    Pardonne moi, mais là il me semble que tu rentres dans le "ça ne se fait pas". Et encore une fois, je n'ai pas d’œil au beurre noir, donc elle ne m'a pas sauté à la figure <je sors>.
    Dépêche toi, avant de recevoir mon 41 fillette dans les fesses
    Je sais, je suis lourd, mais pour moi cette structure a fonctionné jusqu'à présent.
    J'aurai plutôt tendance à dire qu'elle n'a pas explosé si fort que cela... mais ce n'est qu'une question de point de vue
    Cela ne signifie pas que je ne la remettrai pas en cause, mais qu'il y a au moins un cas pour lequel elle a fonctionné, et donc qu'elle risque au moins un peu de pouvoir rendre service (je vais explorer std::any que je ne connaissais pas jusque là, ce qui me permettra peut-être de répondre à ton injonction).
    Mais le fait est que any ne te donne qu'un moyen relativement correct et "facile" de partir sur la force brute.

    Autrement dit, ce n'est jamais qu'un pis aller te permettant de contourner un problème dont la source doit impérativement être tarie, dont la racine doit être impérativement supprimée.

    Car, si tu prend la décision de partir sur de la force brute, tu mets tout en place pour qu'il y ait un maximum de problèmes qui surgissent dans les mois qui viennent et qui t'oblige à faire des détours de plus en plus important pour arriver à les contourner.
    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

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 3
    Dernier message: 09/04/2009, 11h30
  2. [C++] fonction membre template
    Par Luc Hermitte dans le forum BOUML
    Réponses: 3
    Dernier message: 03/02/2009, 23h01
  3. Problème de spécialisation de fonction membres template
    Par ludovic.barrillie dans le forum Langage
    Réponses: 3
    Dernier message: 17/04/2007, 08h44
  4. Pointeur de fonction membre template
    Par bolhrak dans le forum Langage
    Réponses: 6
    Dernier message: 12/12/2006, 14h47
  5. Réponses: 5
    Dernier message: 29/12/2005, 21h27

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