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

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

 C++ Discussion :

j'ai trouvé un usage utile aux multi-class (a fuire si vous n'aimez pas les class fonctor)


Sujet :

C++

  1. #1
    Membre régulier
    Profil pro
    Inscrit en
    Août 2006
    Messages
    78
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 78
    Points : 122
    Points
    122
    Par défaut j'ai trouvé un usage utile aux multi-class (a fuire si vous n'aimez pas les class fonctor)
    Salut,

    tu n'aimes pas les class fonctor ? tu vas pas aimer la suite.

    Dans la plus part des langages de prog (non prototypé)
    comment vous faite un filestream compressé chiffré ?

    exemple en java (fictif)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    var monfile=new FileStream("monfichier.truc")
    var monenc=new Encrypter(monfile,"mon mot de passe")
    var monzip=new zipper(monenc, 9)
    monzip.write(...)
    mouuuais c'est lourd !!!
    selon le langage il faudra fermer les fichiers dans l'ordre

    voyons comment on peut profiter du multiclass CPP pour "fabriquer" un stream comme on le veux

    On definit d'abord la "destination" du stream
    par exemple un fichier, un socket, un port com...

    puis on defini des "modificateur" (par exemple le zipper, le chiffreux, le tar..

    ca pourrais donner un truc comme ca en CPP (fictif):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class MonStream:public FileStream, public Zip, public Enc}{} monStream;
    monStream.setKey("mon mot de passe")
    monStream.open("mon fichier");
    monStream.write(...)
    monStream.close();
    AAaah c'est pas mieux la ?

    le probleme est qu'en CPP les class multiple s'ignore...
    affaire classé...
    ... wait...
    bon, vous connaissez l'adage... c'est parce que on ne savait pas que ce n'etait pas possible
    qu'on la fait..

    Ma 1er tentative etait de passer par un static
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class Server{ // parait qu'il ne faut plus utiliser le mot master c pas sjw...
     static vector<Client>mesclients;
    }
    ... (30 lignes de code)...
    le linker m'a insulté en klingon (a priori le linker n'aime pas trop les static dans les multiclass)

    2eme tentative je passe par un global...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class Client{}
    vector<Client>clients
    ... (30 lignes de code)...
    ca marche plutot bien mais ce n'est pas thread-safe...

    hmm... comment faire pour que mes "modificateur" reçoivent le "this" du "server" lors de la construction..
    j'ecris a tout hasard (j'ai enlevé les "public" pour la lisibilité)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class client{
     client(server *slf){}
    }
    class a:client{}
    class b:client()
    class server{}
    ... main...
    class Myserv:server,a,b{
     Myserv():a(this),b(this){} // <- oui cette iigne est total nawak
    } myserv;
    g++ montruc.cpp

    je m'attends a un flot d'insulte en klingon du sud.
    et la, paf la marmotte fait des chocapic dans du papier alu...
    ca compile trankilou...

    Ah AAAah !!! je tiens ma solution!!!

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    #include <iostream>
    #include <string>
     
    using namespace std;
     
    class ModStream;
     
    using TModStreamIO  =int (ModStream::*)(char** buf,int sz); 
    using TModStreamOCF =int (ModStream::*)();
    using TModStreamSeek=int (ModStream::*)(int pos);
     
    // j'arrive a faire les using de class fonctor maintenant NA !
     
    struct CB{
        ModStream*     ctx;
        TModStreamIO   _read;
        TModStreamIO   _write;
        TModStreamOCF  _open;
        TModStreamOCF  _close;
        TModStreamOCF  _flush;
        TModStreamSeek _seek;
    } cb;
     
    class ServerStream{
        public:
            string type=""; // CTTI :p
            CB cb;
            ServerStream(){
                cb.ctx=NULL;
            }
            virtual int write(char** buf, int sz){
                return (cb.ctx->*cb._write)(buf, sz);
            }
            virtual int read(char** buf, int sz){
                return (cb.ctx->*cb._read)(buf, sz);
            }
    };
     
    class ModStream{
        protected:
            CB oldcb;
            virtual int _read(char** buf,int sz){
                if(!oldcb.ctx) return 0;
                if(oldcb._read) return (oldcb.ctx->*oldcb._read)(buf,sz);
                return -1;
            }
            virtual int _write(char** buf, int sz){
                if(!oldcb.ctx) return 0;
                if(oldcb._write) return (oldcb.ctx->*oldcb._write)(buf,sz);
                return -1;
            }
            virtual int _open(){
                if(!oldcb.ctx) return 0;
                if(oldcb._open)return (oldcb.ctx->*oldcb._open)();
                return -1;
            }
            virtual int _close(){
                if(!oldcb.ctx) return 0;
                if(oldcb._close) return (oldcb.ctx->*oldcb._close)();
                return -1;
            }
            virtual int _flush(){
                if(!oldcb.ctx) return 0;
                if(oldcb._flush) return (oldcb.ctx->*oldcb._flush)();
                return -1;
            }
            virtual int _seek(int pos){
                if(!oldcb.ctx) return 0;
                if(oldcb._seek) return (oldcb.ctx->*oldcb._seek)(pos);
                return pos;
            }
        public:
            ModStream(ServerStream *slf){
                oldcb=slf->cb;
                slf->cb.ctx=this;
                slf->cb._open =(TModStreamOCF)&ModStream::_open;
                slf->cb._close=(TModStreamOCF)&ModStream::_close;
                slf->cb._flush=(TModStreamOCF)&ModStream::_open;
                slf->cb._write=(TModStreamIO) &ModStream::_write;
                slf->cb._read =(TModStreamIO) &ModStream::_read;
            }
    };
     
    class FileStream:public ServerStream{
        protected:
            string filename;
            bool opened=false;       
        public:
            FileStream(){
                cout << "File::"<<endl;
                type="FileStream";
            }
            int open(string fn){
                filename=fn;
                cout << "File::open name="<<filename<<endl;
                opened=true;
                return (cb.ctx->*cb._open)();
            }
            int flush(string fn){
                cout << "File::flush name="<<filename<<endl;
                return (cb.ctx->*cb._open)();
            }
            int close(){
                int err=(cb.ctx->*cb._close)();
                cout << "File::close name="<<filename<<endl;
                return err;
            }
            virtual int read(char** buf, int sz){
                cout <<"File::read in "<<filename<<endl;
                return ServerStream::read(buf,sz);
            }
            virtual int write(char** buf, int sz){
                cout <<"File::write in "<<filename<<endl;
                int err= ServerStream::write(buf,sz);
                return err;
            }
    };
     
    class Enc:public ModStream{
        private:
            string key;
            bool opened=false;
        protected:
            virtual int _read(char** buf,int sz){
                cout << "Enc::read"<<endl;
                return ModStream::_read(buf,sz);
            }
            virtual int _write(char** buf, int sz){
                cout << "Enc::write key="<<key <<endl;
                return ModStream::_read(buf,sz);
            }
            virtual int _open(){
                if(key.empty()) return -1;
                opened=true;
                cout << "Enc::open key="<<key << endl;
                return ModStream::_open();
            }
            virtual int _close(){
                cout << "Enc::close"<<endl;
                return ModStream::_close();
            }
            virtual int _flush(){
                cout << "Enc::flush"<<endl;
                return ModStream::_flush();
            }
            virtual int _seek(int pos){
                cout << "Enc::seek"<<endl;
                return ModStream::_seek(pos);;
            }
        public:
            Enc(ServerStream* slf):ModStream(slf){
                slf->type+="+Enc";
                cout << "Enc::"<<endl;
            }
            int setKey(string k){
                if (opened) return -1;
                key=k;
                return 0;
            }
    };
     
    class Zip:public ModStream{
        public:
            int ratio=9;
            bool opened=false;
        protected:
            virtual int _read(char** buf,int sz){
                cout << "Zip::read ratio="<<ratio<<endl;
                return ModStream::_read(buf,sz);
            }
            virtual int _write(char** buf, int sz){
                cout << "Zip::write"<<endl;
                return ModStream::_read(buf,sz);
            }
            virtual int _open(){
                cout << "Zip::open ratio="<<ratio<<endl;
                return ModStream::_open();
            }
            virtual int _close(){
                cout << "Zip::close"<<endl;
                return ModStream::_close();
            }
            virtual int _flush(){
                cout << "Zip::flush"<<endl;
                return ModStream::_flush();
            }
            virtual int _seek(int pos){
                cout << "Zip::seek"<<endl;
                return ModStream::_seek(pos);
            }
        public:
            Zip(ServerStream* slf):ModStream(slf){
                slf->type+="+Zip";
                cout << "Zip::"<<endl;
            }
            int setRatio(int r){
                if(opened) return -1;
                ratio=r;
                return 0;
            }
    };
     
    // ce que le ender user ecrira //
    class MyStream:public FileStream,public Zip,public Enc{
        public:
            MyStream():Zip(this),Enc(this){};
    };
     
    int main(int argc, char** argv){
     MyStream myStream;
     cout << myStream.type << endl;
     myStream.setKey("1234");
     myStream.setRatio(5);
     cout << myStream.open("totor.txt") << endl;
    }
    en sorti d'ecran
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    (construction de l'objet myStream)
    File:: 
    Zip::
    Enc::
    (contenu de myStream.type)
    FileStream+Zip+Enc
    (mySteam.open)
    File::open name=totor.txt <- myStream.open
    Enc::open key=1234 <- Enc recois l'open 
    Zip::open ratio=5 <-Zip recois l'open
    (cout << open)
    0 <-code d'erreur de l'open
    reste à remplacer les cout par du vrai code...

    on pourra rajouter d'autre server, d'autre modificator
    des buffered, de l'IO bit a bit, un tar/fakestream etc etc...

    ou pas...

    5u

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

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 628
    Points : 10 553
    Points
    10 553
    Par défaut
    C'est bien tu as appris 1 chose le patron de conception décorateur ("decorator" en anglais)

    Tes 2 classes Enc et Zip sont des décorateurs, ModStream étant l'interface.

    Mais au lieu de stocker 1 objet ServerStream, tu utilises l'héritage multiple : est-ce mieux ? je ne le sais pas.
    Et ModStream devrait dériver de ServerStream ... ou simplement la supprimer parce qu'elle ne sert à rien, juste de "wrapper" et faire dériver tes décorateurs de ServerStream.


    Édit : en cherchant plus loin tu as la notion de mixin (<- lien wikipedia en anglais), que wikipedia appelle "static decorator" sur la page du décorateur en anglais.
    Certains langages n'ayant pas l'héritage multiple ont ce concept pour en faire. Dans la page wikipedia, c'est décrit comme 1 interface avec des méthodes implémentées (<- la différence entre 1 trait et 1 mixin, c'est que le mixin peut avoir des membres/ états)

    Et en C++, les mixins sont codés essentiellement avec le patron CRTP, page wikipedia en anglais

  3. #3
    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,

    Ce que les gens n'arrivent pas à comprendre, c'est que, lorsque l'on écrit un code en java qui est proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public class MaClass:  inherits Base, implement Interface{
     
    };
    ce qu'ils font, conceptuellement parlant, c'est bel et bien déjà du "multi classes" au sens du principe de substitution de Liskov.

    Pire encore: même si on venait à retirer le inherits Base, nous resterions toujours dans le "multi classes" du simple fait de l'héritage implicite de la classe Object pour notre classe

    Il est donc, quelque part, assez marrant de constater que les plus gros détracteurs d'une technique soient en réalité ceux qui en font -- sans même s'en rendre compte -- l'usage le plus intensif
    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

  4. #4
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 113
    Points : 32 960
    Points
    32 960
    Billets dans le blog
    4
    Par défaut
    Ça sent bon l'héritage en diamant non contrôlé.
    Ce ne sont pas des class foncteur mais des pointeurs de fonctions membres.
    Depuis qu'on a std::function et les lambdas, c'est trivial à utiliser. Et bien plus souple.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  5. #5
    Membre régulier
    Profil pro
    Inscrit en
    Août 2006
    Messages
    78
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 78
    Points : 122
    Points
    122
    Par défaut
    Citation Envoyé par Bousk Voir le message
    Ça sent bon l'héritage en diamant non contrôlé.
    Ce ne sont pas des class foncteur mais des pointeurs de fonctions membres.
    Depuis qu'on a std::function et les lambdas, c'est trivial à utiliser. Et bien plus souple.
    ok.. moi j'avais comme definition:
    functor : pointer sur function
    donc par extention class functor...
    forcement sur les membres parce que sinon je vois pas...
    ca chiopolata un peu la non ?

    bah justement je suis en train d'apprendre...
    j'irais jeter un oeil sur std::function voir...

    mais j'ai lu pas mal de gens qui gueule sur la lenteur (supposé) du RTTI cpp.
    (en resumé dynamic_cast c'est le Mal !)
    je ne connais suffisament le CPP pour juger

    c'est plutot marrant de jouer avec l’équivalant du RTTI en java.
    (le nom m'echappe a l'instant)

    si tu as un exemple de std::function je veux bien.
    pas mal d'exemple que je trouve sont vraiment insipide

    5u

    Citation Envoyé par koala01 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    public class MaClass:  inherits Base, implement Interface{};
    ce qu'ils font, conceptuellement parlant, c'est bel et bien déjà du "multi classes" au sens du principe de substitution de Liskov.
    tout a fait d'accord... surtout depuis qu'on peux mettre des default dans les interfaces.

    Citation Envoyé par koala01 Voir le message
    même si on venait à retirer le inherits Base, nous resterions toujours dans le "multi classes"
    Oo explique ???

    5u

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

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 628
    Points : 10 553
    Points
    10 553
    Par défaut
    Citation Envoyé par foxzoolm Voir le message
    functor : pointer sur function
    1 foncteur ("functor" ou "function object" en anglais), c'est 1 classe que tu surcharges l'opérateur (), et ainsi l'appeler comme 1 fonction.
    D'ailleurs on appelle 1 foncteur 1 fonction à états, parce qu'effectivement, via les membres, elle peut garder 1 ou plusieurs états.

    Citation Envoyé par foxzoolm Voir le message
    (en resumé dynamic_cast c'est le Mal !)
    Il y a 4 casts en c++ : const_cast, static_cast, reinterpret_cast, dynamic_cast (c'est pour faire du downcasting de classe)

    le + dangereux c'est le reinterpret_cast, qui prend 1 "chunk" et le transforme en variable du type que tu veux.


    Citation Envoyé par foxzoolm Voir le message
    c'est plutot marrant de jouer avec l’équivalant du RTTI en java. (le nom m'echappe a l'instant)
    Cela s'appelle de la réflexion (page wikepedia en français et celle en anglais)
    Et contrairement au Java, en C++ il manque la partie où on peut créer 1 classe avec son nom en chaîne de caractères.

  7. #7
    Membre régulier
    Profil pro
    Inscrit en
    Août 2006
    Messages
    78
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 78
    Points : 122
    Points
    122
    Par défaut
    Citation Envoyé par foetus Voir le message
    tu utilises l'héritage multiple : est-ce mieux ? je ne le sais pas.
    parce qu'on peut le faire...

    perso, j'ai été tres frustrer par les declarations imbriqué en cascade "à la java".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    var a=new A();
    var b=new B(a)
    var c=new C(b)
    var d=new D(c)
    etc.. etc... etc
    et si ne peux pas mettre ca dans un try/ressource c'est l'enfer.

    dans mon exemple les class sont "parametrer" a l'init.
    ca arrive que tu es besoin de modifié un truc dans ta cascade.
    donc conserver l'instance (pas de new en cascade)

    par exemple; (code fictif)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    window w;
    title t(w)
    scrollbar s(w)
    status st(w)
    etc
    avec des t.setTitle partout pour le wizzzz
    et tu multiplie les window...
    ca deviens vite tres chiant a maintenir
    plus simple d'avoir 1 objet (par window) sur lequel faire les interactions

    Citation Envoyé par foetus Voir le message
    ModStream devrait dériver de ServerStream ... :
    c'est justement ca que je veux eviter.... les declarations imbriqué en cascade
    (ou appel ca comme tu veux)

    c'est meme la principale raison (le multiclass) pour lequel je m'interesse au CPP.
    .
    Citation Envoyé par foetus Voir le message
    Certains langages ont ce concept (mixin)
    oui oui, j'ai vue ca dans le langage D...

    mais clairement, ce qui fait un langage de programmation c'est surtout la taille de ca bibliothèque.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par foxzoolm Voir le message
    Citation Envoyé par koala01
    même si on venait à retirer le inherits Base, nous resterions toujours dans le "multi classes"
    Oo explique ???
    Ben, comme je l'ai expliqué juste après, même lorsque tu crées une classe qui n'hérite d'aucune classe de base "perso" et qui n'implémente aucune interface particulière en java, tu te retrouve -- malgré tout -- avec une classe qui hérite ** implicitement ** de la classe Object, condition de base pour pouvoir la faire passer par le ramasse miettes.

    Si tu as une classe proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    public class MyClass{
    public void doSomething(){
        /* ... */
    }
    };
    en java (ou en C#) , le compilateur va d'office la faire hériter de la classe Object, sans rien te dire de particulier, ce qui revient au final exactement au même que si tu avais une classe proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    public class MyClass : inherits Base{
    public void doSomething(){
        /* ... */
    }
    };
    (car Base va sans doute implicitement hériter de Object, à moins que ce ne soit la classe la plus haute dans la hiérarchie de classes de la classe Base qui le fasse): quelle que soit la classe que tu définis en java ou en C#, il y a d'office un héritage implicite ou explicite, en fonction du code que tu vas écrire

    Si bien que, dés le moment où ta classe va implémenter une interface quelconque, tu te retrouve dans une situation dans laquelle, au sens de LSP, tu as déjà un double héritage qui par définition est "multi classes" vu que multi commence à "plus de un"
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  9. #9
    Membre régulier
    Profil pro
    Inscrit en
    Août 2006
    Messages
    78
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 78
    Points : 122
    Points
    122
    Par défaut
    Citation Envoyé par foetus Voir le message
    1 foncteur ("functor" ou "function object" en anglais), c'est 1 classe que tu surcharges l'opérateur (),
    je n'avais pas cette definition du tout...
    j'ai "rencontré" le mot "functor" dans des langages non poo, sans overload d'operateur...
    et typiquement dans des cas de pointer sur function
    (en turbopascal 4 de memoire)
    mais pointer sur fonction membre... ca pete pas la class... (ahahahaha)
    bref osef.
    ..
    Citation Envoyé par foetus Voir le message
    Il y a 4 casts en c++ : const_cast, static_cast, reintrepret_cast, dynamic_cast[/code]
    oui oui, je rtfm un peu quand meme...
    mais les exemples sont insipide et je n'arrive pas a comprendre ni l'interet ni dans quel cas ca s'utilise...
    (par rapport a un cast "classique")

    Citation Envoyé par foetus Voir le message
    reintrepret_cast, qui prend 1 "chunk" et le transforme en variable du type que tu veux.
    par exemple les void* ?

    (a propos du RTTI en java)
    Citation Envoyé par foetus Voir le message
    Cela s'appelle de la réflexion
    voila !!! pourtant LRI sait combien je l'ai utilisé...

    Citation Envoyé par foetus Voir le message
    en C++ il manque la partie où on peut créer 1 classe avec son nom en chaîne de caractères.
    ouais mais java il triche... (je pense qu'on peux faire ca aussi en c#)
    d'ailleurs c'est presque le seul interet du java... et qui le rend si lent avant la passe de JIT.

    Citation Envoyé par koala01 Voir le message
    ta classe va implémenter une interface quelconque
    Aaaaaaaaaaaah okey... bien vue !!!

    par contre, une "interface" n'est pas suffisante pour appeler ca "multiclass" au sens de moi-meme.

    mais je suis d'accord si (et seulement si) l'interface propose des (fonction membre) defaults...
    il me semble que ce n'est pas le cas en java8.

    a priori, je devrais pouvoir ecrire l’équivalant de mon code CPP en java.

    Citation Envoyé par foetus Voir le message
    en C++, les mixins sont codés essentiellement avec le patron CRTP, page wikipedia en anglais
    tu vas rire (ou pas) : c'est EXACTEMENT ca que j'ai utilisé dans le code precedament posté ici
    (a propos des property comme en C#)
    et la j'ai un parfait exemple de demo de static_cast !!!

    1000x merci... c'est exactement ca que je cherche depuis des jours !!!

    il y a juste une détail qui m’échappe...
    il (ms) utilise static_cast au lieu d'un cast "à la C" uniquement parce que
    "c'est plus moderne" / "lisible" etc...
    ou j'ai raté un truc ?

  10. #10
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    J'ai l'imressop, qu'il y a pourtant quelque chose de fondamentalement entre ton code d'origine et ton code de destination. La manière de construire est très différente et implique des utilisations qui n'ont pas grand chose à voir.

    Au départ, tu pars d'un pattern qui ressemble à un décorateur, qu'on retrouve justement beaucoup en Java (https://stackoverflow.com/questions/...ern-in-java-io ou http://cecs.wright.edu/~tkprasad/cou...er/node26.html). Grâce à ce pattern, tu peux facilement créer un objet de base et ajouter au fur et à mesure des capacités. Dans l'exemple des streams Java : tu crées un stream qui lit un port série, tu le wrappes dans un stream bufferisé, puis un stream qui filtre, etc. Tu es libre des choix des différentes couches.

    A l'arrivée, tu as juste figé un groupe de couches. Peut-être plus pratique si tu crées toujours le même assemblage, mais c'est tout.

    Bref, je rejoins le premier message de Foetus.

    il (ms) utilise static_cast au lieu d'un cast "à la C" uniquement parce que
    "c'est plus moderne" / "lisible" etc...
    ou j'ai raté un truc ?
    C'est une bonne pratique d'utiliser les opérateurs de cast en C++. dynamic_cast n'existe pas en C. Les 3 autres expriment plus clairement ce que tu veux faire. On peut rechercher les casts dans un projet, ce qu'on ne peut pas faire les casts C.

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

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 628
    Points : 10 553
    Points
    10 553
    Par défaut
    Citation Envoyé par foxzoolm Voir le message
    oui oui, je rtfm un peu quand meme...
    mais les exemples sont insipide et je n'arrive pas a comprendre ni l'interet ni dans quel cas ca s'utilise...
    (par rapport a un cast "classique")
    après vérification (mais à vérifier), le cast classique (le cast "C") fait toujours const_cast : il transforme 1 variable constante en variable non constante.
    Et ensuite le cast "C" fait 1 static_cast. Et s'il n'y arrive pas il fait 1 reinterpret_cast.

    C'est assez subtil, mais effectivement (mais à vérifier) static_cast peut modifier la variable mais pas reinterpret_cast (elle prend le "chunk" tel qu'il est)
    Par exemple, lorsque tu castes 1 entier en flottant, ton entier doit être transformé pour être 1 flottant (IEEE 754, sous forme de signe, exposant, mantisse)
    C'est ce que fait static_cast.
    C'est pour cela que c'est écrit que 2 reinterpret_cast consécutifs redonne le résultat original.


    Citation Envoyé par foxzoolm Voir le message
    par exemple les void* ?
    Le void* sert à faire la généricité en C. Donc l'utiliser en C++, il faut avoir 1 très bonne raison


    Citation Envoyé par foxzoolm Voir le message
    il y a juste une détail qui m’échappe...
    il (ms) utilise static_cast au lieu d'un cast "à la C" uniquement parce que
    "c'est plus moderne" / "lisible" etc...
    ou j'ai raté un truc ?
    j'ai retrouvé la citation du créateur du C++ Bjarne Stroustrup dans son premier livre "C++ style casts are intentionally ugly, because they are also potentionally dangerous."

    En gros, ne pas faire de casts

  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 foxzoolm Voir le message
    Aaaaaaaaaaaah okey... bien vue !!!

    par contre, une "interface" n'est pas suffisante pour appeler ca "multiclass" au sens de moi-meme.
    Ah bon

    Alors, peut-être pourras tu m'expliquer ce que l'on peut faire avec une classe que l'on ne puisse pas faire, conceptuellement parlant, avec une interface (ou l'inverse)

    Attends, laisse moi deviner ta réponse : c'est pourtant évident: une interface ne peut pas être instanciée par elle-même: il faut qu'elle soit implémentée par une classe pour que cela puisse se faire... Me trompes-je

    Soit, cela semble cohérent. Mais, du coup, apprête toi à te mettre le cerveau à l'envers à cause des deux questions suivantes : Et quoi, toutes les classes sont-elles forcément instanciables "par elles-mêmes Et, qu'en est-il des classes abstraites

    De plus, dis moi si je me trompe, mais il me semble qu'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
    public interface MyInterface{
    public void foo(); // it works even without default implementation
    };
    public class Base{
    public void bar(){
        /* maybe some default implementation */
    }
    };
    public class MyClass : inherits Base, implements MyInterface{
    /* ... */
    }; 
    public class Client{
    static private void useIface(MyInterface iface){
        iface.foo(); // certainly polymorphic call to foo()
    }
    static private void useBase(Base b){
        b.bar(); // maybe polymorphic call to bar()
    }
    static void main(){
        MyClass mc = new MyClass;
        useBase(mc);
        usIface(mc);
    }
    };
    est tout à fait légal, autorisé et admis en java, non

    (Notes bien que je pourrais être plus vache et te demander de citer le principe de conception qui régit la notion d'interface, étant entendu que, si LSP régit la notion de classes et que, si les interfaces sont conceptuellement différentes des classes, c'est qu'il doit y avoir un principe de conception différent à leur sujet )

    Le fait est que c'est strictement le même principe conceptuel qui régit les classes et les interface: la substituabilité telle qu'exprimée par Liskov; que la distinction entre les mots clés class Vs interface et, partant, entre les mots clés inherits Vs implements n'est que la conséquence (plus ou moins) "directe" du souhait de fournir un système de gestion dynamique de la mémoire "intelligent" (le ramasse miettes ou garbage collector):

    Car, pour que ce système puisse fonctionner, il faut que toutes les données pour lesquelles nous aurons recours à l'allocation dynamique de la mémoire (pour lesquelles nous utiliserons l'opérateur new puissent être connues de ce système particulier. Et, pour y arriver, il faut que les types correspondant à ces données héritent tous (de manière directe ou indirecte, de manière implicite ou explicite) d'une classe de base (la classe Object).

    Sauf que, si on prend ce chemin là, le risque de se retrouver avec un héritage "en diamant" est patent, ce qui poserait de sérieux problèmes

    Une solution pourrait donc être d'utiliser l'héritage virtuel pour les classes directement dérivées de la classe Object. Sauf que cela occasionnerait bien plus de problèmes qu'autre chose. Cette solution a donc été rejetée "en bloc".

    Par contre, si on crée "artificiellement" une distinction entre deux groupes de "classes" en appelant les première (qui héritent, de manière directe ou indirecte de Object) "classes" et les deuxièmes "interfaces", en s'assurant que les deuxième ne puissent jamais nous mettre en situation d'héritage en diamant, et que l'on renforce cette distinction artificielle en disant que l'on hérite des premières et qu'on implémente les deuxièmes, alors, on peut jouer sur les règles du compilateur pour
    1. qu'il n'accepte qu'une seule instruction d'héritage
    2. que si l'instruction d'héritage est absente, il rajoute automatiquement (et sans rien dire à personne) une instruction d'héritage depuis la classe Object
    3. qu'il autorise la présence de plusieurs instructions d'implémentation

    Et, du coup, on pourra crier haut et fort que notre langage ne supporte pas l'héritage sous typage (je joue sur les mots, car LSP parle de sous typage, non d'héritage ) multiple, alors que les bonnes pratiques consistent justement à user et à abuser du deuxième groupe de "classes" (les interfaces, au cas où tu ne m'aurais pas suivi ) et donc à user et à abuser du principe même de l'héritage du sous-typage multiple.

    Dis toi bien que ce n'est pas parce qu'on l'appelle "implémentation" que la relation que l'on crée entre une classe et une interface est autre chose qu'une relation d'héritage au sens du LSP!!! On aurait pu l'appeler tartempion, ce serait revenu au même (mais cela aurait été beaucoup moins sérieux ) car nous parlons bel et bien de sous-typage


    mais je suis d'accord si (et seulement si) l'interface propose des (fonction membre) defaults...
    il me semble que ce n'est pas le cas en java8.
    Il ne faut même pas attendre que le langage nous autorise les implémentations par défaut! Tu n'as qu'à relire le principe de Liskov pour t'en convaincre:
    Si f(x)est une propriété démontrable pour tout objet x de type T, alors f(y) est vraie pour tout objet y de type S tel que S est un sous type de T
    Dés le moment où une interface expose un comportement quelconque, comme par exemple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public interface MyInterface{
    public void foo();
    };
    il n'y a même pas besoin que le comportement de foo soit implémenté pour dire que "foo est une propriété démontrable de MyInterface", vu que c'est le but premier de notre interface.

    Mais cela signifie que toute classe qui va implémenter notre interface sera -- forcément -- un sous type de notre interface. La preuve étant que l'on peut transmettre une instance de notre classe à toute fonction s'attendant à recevoir (une instance, ce qui est un comble pour quelque chose de non instanciable, pas vrai de) notre interface.
    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
    Août 2006
    Messages
    78
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 78
    Points : 122
    Points
    122
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Dans l'exemple des streams Java : tu crées un stream qui lit un port série, tu le wrappes dans un stream bufferisé, puis un stream qui filtre, etc. Tu es libre des choix des différentes couches.
    comment tu ecris ca en java ? (fictif)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    var f=new comStream(port)
    var b=new bufferedSteam(f)
    var c=new filterSteam(b);
    etc...
    non ou bien ?

    on est d'accord que si je veux par exemple changer le BPS du com ou je ne sais pas
    quoi dans l'un des "wrapper" tu dois "conserver" les instances (f,b,c)
    imagine que tu as 300 port com avec des "decorateurs" differants.
    300xnombre d'instance a conservé (par exemple la 3)= 900 variables...
    et que tu ne peux pas les mettre dans un try/ressource (LRI que ca arrive souvant)
    ou l'un des wrapper est codé avec les pieds (n'implemente pas autoclose)
    ...

    Citation Envoyé par Bktero Voir le message
    A l'arrivée, tu as juste figé un groupe de couches. Peut-être plus pratique si tu crées toujours le même assemblage, mais c'est tout.
    Baaaah non... je peux justement construire mon stream comme je veux..

    il n'y a aucun nuance avec les wrapper java...
    sauf que je rajoute un couche multiclass... donc 1 variable ou lieu de 1 par wrapper...

    pour rajouter un "decorateur" il n'y a qu'a derrivé la class modStream.
    (j'ai appelé ca des "modificateur")
    [pas FORCEMENT par l'utilisateur finale hein]

    comment j'ecrirais mon port com stream buffered filtré avec mon code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    class Cmoncom=public ComStream,public BufStream, public FilterStream ETC ETC ETC{
     Cmoncom():ComStream(this),BufStream(this),FilterStream(this) ETC ETC ETC
    } moncom;
    la ou c'est magique (je pense que tu as rater ce detail...)
    moncom du fait qu'il est multi-classer...
    accede a tout les membres public de
    ComStream,BufStream,FilterStream....
    tu veux changer le BPS
    moncom.setBPS(...)
    tu veux changer la taille du buf (osef)
    moncom.setBuf(...)
    tu veux changer ton filter
    moncom.setFilter(...)

    ou alors je pige pas un truc...

    Citation Envoyé par foetus Voir le message
    Bjarne Stroustrup dans son premier livre "C++ style casts are intentionally ugly, because they are also potentionally dangerous."
    En gros, ne pas faire de casts
    Heureusement les cast n'existe que CPP..

    Citation Envoyé par koala01 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public interface MyInterface{
    public void foo(); // it works even without default implementation
    };
    est tout à fait légal, autorisé et admis en java...
    [edit] mes yeux mon trompé, j'ai crue lire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public interface MyInterface{
    public void foo() {} // <-- avec les accolades
    };
    euuuuh... du coup faut que je verifie un truc...

  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 foxzoolm Voir le message
    comment j'ecrirais mon port com stream buffered filtré avec mon code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    class Cmoncom=public ComStream,public BufStream, public FilterStream ETC ETC ETC{
     Cmoncom():ComStream(this),BufStream(this),FilterStream(this) ETC ETC ETC
    } moncom;
    la ou c'est magique (je pense que tu as rater ce detail...)
    moncom du fait qu'il est multi-classer...
    accede a tout les membres public de
    ComStream,BufStream,FilterStream....
    tu veux changer le BPS
    moncom.setBPS(...)
    tu veux changer la taille du buf (osef)
    moncom.setBuf(...)
    tu veux changer ton filter
    moncom.setFilter(...)

    ou alors je pige pas un truc...
    Ben, a partir du moment où ltu vas écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class Cmoncom=public ComStream,public BufStream, public FilterStream ETC ETC ETC{
     Cmoncom():ComStream(this),BufStream(this),FilterStream(this) ETC ETC ETC
    } moncom;
    tu fixe toi même dans le code l'ensemble des éléments qui seront forcément utilisés lorsque l'utilisateur décidera d'utiliser une instance de ta classe:

    Tu ne lui donne ni l'occasion de modifier l'ordre dans lequel les différents éléments seront utilisés, car elle sera fixée par toi-même (développeur de la classe Cmoncom) une bonne fois pour toute (à moins que l'utilisateur ne se mette à créer une classe dérivée de Cmoncom pour redéfinir l'ordre dans lequel les différents éléments seront utilisés, mais, dés lors, quel est l'intérêt de le faire directement ), et tu ne lui donne pas d'avantage la possibilité de rajouter des éléments dans l'ordre qu'il souhaite, vu que le code va être compilé.

    Et, pire encore: tu ne laisse même pas à l'utilisateur de ta classe la possibilité de ne pas utiliser un des éléments que tu as prévu: si tu as prévu d'avoir un filtre (un buffer ou n'importe quoi d'autre), il faut qu'un filtre (ton buffer ou n'importe quoi d'autre) existe bel et bien (en fait: un filtre (un buffer ou n'importe quoi d'autre) sera d'office créé, vu que c'est une des classe de base de CmonCom) même s'il vient à ne rien faire: ca fait littéralement partie de ta classe Cmoncom, et donc, tu ne sais pas ni le retirer, ni en empêcher la création.

    Ce qui est vraiment dommage, car, même si toutes ces parties ne sont rien sensées faire, leur création (pour les rendre "utilisables") risque de demander pas mal de ressources (en temps et autre) qui seront "inutilement" dépensées si ces parties ne font rien

    Enfin, d'un point de vue purement conceptuel, l'héritage en lui même est largement discutable.

    En premier lieu, parce que, si je suis d'accord sur le fait ce Cmoncom est -- de toute évidence -- un flux de donnée, je ne suis pas sur du tout que l'on puisse affirmer avec certitude que Cmoncom soit, à la fois, un flux com, un flux "buffer", un flux "filtrant" et un flux de toutes les autres parties que tu rajoute.

    Mais, surtout, parce qu'il faut bien te rendre compte que le simple fait de faire hériter Cmoncom de toutes ces parties te permet de substituer une instance de Cmoncom à n'importe quelle instance de n'importe laquelle de ses classes de base, c'est à dire, à n'importe quelle fonction s'attendant à recevoir
    • un flux com OU
    • un flux "buffer" OU
    • un flux "filtrant" OU
    • n'importe quel autre flux correspondant aux autres parties dont tu l'aurais fais hériter.

    Et ca, ca va nous poser un problème, car cela signifie -- en gros -- que tous les flux vont être manipulé de la même manière. En gros, avec les opérateurs << et >>.

    Oui, mais bon... Il faut pas oublier que le comportement des opérateur << et >> va être défini (une bonne fois pour toutes) de manière à utiliser chacun des flux qu'il implémente dans un ordre bien précis, et qu'il n'y aura pas moyen de ne prendre que la partie de ces opérateurs qui nous intéresse

    Du coup, tu vas transmettre ton instance de Cmoncom à une fonction qui s'attend -- par exemple -- à recevoir un flux filtrant, et tu auras la surprise de constater que le flux com a été ouvert, que le buffer a été défini et que la compression s'est effectuée, alors que ce n'était absolument pas ce qui était sensé se passer

    (et je ne parle même pas du problème que tu risques de rencontrer si tes parties ComStream, BufStream, FilterStream et autres venaient par malheur toute à hériter d'une classe de base identique comme i/o stream )

    Il est donc largement temps de se rappeler que l'héritage est la relation la plus forte qui puisse exister entre deux classes, et que c'est donc celle qu'il ne faudra utiliser que si toutes les autres solutions (dont l'agrégation) sont absolument inutilisables / inefficaces.

    Car, dans le cas présent, ce qui serait largement préférable, ce serait
    • de faire -- éventuellement -- hériter Cmoncom de la notion de flux de donnée (i/o stream)
    • d'ajouter un comportement permettant d'ajouter un (ou plusieurs) traitement, dans l'ordre dans lequel on veut les voir s'exécuter
    • (éventuellement, de définir un ordre de traitement une fois que tous les traitements ont été ajouté)
    • de définir l'opérateur << / >> pour que les traitements soient effectués "dans l'ordre souhaité".
    • éventuellement, de fournir une interface permettant de récupérer les différents flux par lesquels l'information doit passer lors de son traitement
    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
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 628
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 628
    Points : 10 553
    Points
    10 553
    Par défaut
    Il nous manque le contexte mais si c'est faire 1 "stream" (ouvrir, écrire, fermer) et ses traitements (chiffrer, compresser, filtrer, ...) je trouve ton code trop lourd
    Déjà ce ne sont pas aux traitements d'ouvrir/ fermer/ ... ton "stream".
    Et c'est peut-être là ton problème : 1 décorateur est fait pour "décorer" 1 appel (ajouter 1 responsabilité essentiellement), mais pas faire "le café".

    1 idée comme cela (comme std::algo): faire des foncteurs "process", et ensuite dans ta classe faire 1 tableau de traitements. Tu parcours ton tableau et passes ton "buffer" à chaque fois.
    Mais on retombe sur ton problème (avoir X variables). Et par rapport à 1 décorateur, cela revient au même à vue de nez.

    1 autre idée : faire 1 collection de traitements (fonctions ou fonctions amies (friend en C++)) et les appeler quand tu veux.
    Le problème, tu perds les membres (dans ton code, tu stockes 1 clef de compression et 1 ratio de compression)

    Dans ton cas, au vu
    • du nombre de traitements, 2 ou 3. Cela fait pas beaucoup.
    • du peu de variables pour chaque traitement.
    • de la fixation des traitements avec ton héritage multiple. Et pire, à chaque modification (ajout ou suppression d'1 traitement), tu dois recompiler.

    Moi, je commencerais par faire 1 "grosse" classe avec tout dedans : cela revient presque au même (peut-être la création et la destruction + la compilation seront 1 chouïa + rapide) mais en supprimant à peu près 70% du code et en n'ayant pas les problèmes de l'héritage multiple "en embuscade"

  16. #16
    Membre régulier
    Profil pro
    Inscrit en
    Août 2006
    Messages
    78
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 78
    Points : 122
    Points
    122
    Par défaut
    Citation Envoyé par koala01 Voir le message
    car elle sera fixée par toi-même (développeur de la classe Cmoncom)
    ok;.; je viens de comprendre la confusion...
    comme j'ai tout mis dans le meme code...
    tu as surement pensé que JE defini "monstream" dans ma lib...

    ok ma faute... je me suis du mal expliquer...

    la lib principale n'est consistué que des class "abstract".(modstream et serverstream)
    puis de libs secondaire (chacun separé)
    - encstream
    - filestream
    - netstream
    - zipstream
    - etc

    le developpeur final ne fait que des #import
    puis construit son stream comme il veut
    (la construction est un peu lourd c'est vrai)

    un truc du genre:

    --> ce qu'ecrit le developpeur final dans son code <--
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    #import <filestream>
    #import <zipstream>
    ...
    class cmonstream:filestream,zipstream // etc
    donc il choisit ce qu'il veux...

    d'autre developpeur pourrons cree leurs propre "modificateur" en dérivant "modstream"
    pour par exemple ajouter un meilleurs zipper (par exemple lzma)
    ou un encrypter plus solide (par exemple AES)
    puis les proposer aux developpeur final.

    encore une fois : le developpeur final n'import QUE les modificateur qui veux et cree sont propre
    multi-class...

    désolé de ne pas avoir été assez claire sur ces points.

    mais nous somme d'accord que ca ne marche qu'a la compilation...
    bien que, avec un peu d'astuce tu devrais pouvoir construire le stream en runtime
    mais en perdant l'aspect "multiclass".

    NB : j'appel "developeur final" celui qui vas utilisé la lib dans son code
    et "developpeur" celui qui ajoute des elements à la lib.
    tu as utilisé "utilisateur" qui peut etre confus : pour moi l'utilisateur est celui qui utilise le logiciel.

    NB2: je ne maitrise pas bien encore les .hpp vs .cpp (surtout niveau linkage)

    je pense que tu dois aussi lire ma reponse a koala.

    Citation Envoyé par foetus Voir le message
    ce ne sont pas aux traitements d'ouvrir/ fermer/ ... ton "stream"
    ils ne le ferment pas mais recoivent juste un "event"
    ca permet par exemple au Enc de s'init avec le key.
    je ne voulais pas trop surcharger les constructors.
    c'est assez difficile de surcharger et parametrer (avec autre chose que des immediats) un constructor en multi-class...
    (en tout cas moi je galere)
    donc pour etre le plus "generique" possible, je passe par des "event'.
    si "open"/"close" te derrange, appel ca "init" et "done"...

    Citation Envoyé par foetus Voir le message
    mais pas faire "le café".
    mais s'il sait faire le café... pourquoi sans privée ?
    (bon c un mauvais exemple : je ne bois pas de café)

    Citation Envoyé par foetus Voir le message
    [*]du nombre de traitements, 2 ou 3. Cela fait pas beaucoup.
    si tu n'as que 1 seul stream dans ton programme...

    (vecu souvant) si tu as 10 streams en entree 3 en sortis....
    ca donne combien de variable a maintenir ?
    et tu as interet a BIEN les nommer !
    en plus tu devra fermer des wrappers dans l'ordre.
    (je trouverais ca "cavalier" pour un wrapper zip de fermer le stream)

    [j'ai une curieuse sensation de me repeter]

    Citation Envoyé par foetus Voir le message
    [*]de la fixation des traitements avec ton héritage multiple.
    donc lire ma reponse a koala...

  17. #17
    Membre régulier
    Profil pro
    Inscrit en
    Août 2006
    Messages
    78
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 78
    Points : 122
    Points
    122
    Par défaut
    Citation Envoyé par koala01 Voir le message
    ... a propos de "peut on dire que java est multi-class"...
    bon effectivement ca m'a pété un neuronne...
    tu as de bon argument...
    y a quand meme des trucs qui me chipotouille..
    mais plus je trouve d'argument plus je trouve qu'au finale tu as raison...
    par extention : alors C# est multi-class...
    sauf que...
    le contre argument qui tue : eux meme (les concepteurs) disent qu'ils ne sont pas
    multi-class...
    ce qui dit donnerais un conseil de Yaourt : "avec de l'astuce"...
    mais je pense que ce n'est pas le lieu pour en debattre.

  18. #18
    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 foxzoolm Voir le message
    ok ma faute... je me suis du mal expliquer...

    la lib principale n'est consistué que des class "abstract".(modstream et serverstream)
    puis de libs secondaire (chacun separé)
    - encstream
    - filestream
    - netstream
    - zipstream
    - etc

    le developpeur final ne fait que des #import
    puis construit son stream comme il veut
    (la construction est un peu lourd c'est vrai)
    Que la classe finale (Cmoncom soit dans une bibliothèque ou directement dans l'application ne change absolument rien: elle est spécifique à un usage bien particulier parmi un ensemble combinatoire beaucoup trop important.

    Car, si on ne prend que quatre éléments possibles (ComStream, CompressStream, BufferStream et FilterStream) et que tu travailles sur une application dont les spécifications impliquent clairement de travailler sur un port com, tu te retrouve déjà avec la possibilité d'avoir sept classes différentes
    1. ComStream (un flux de base vers ton port com)
    2. ComStream + BufferStream (un flux bufferisé vers ton port com
    3. ComStream + BufferStream + FilterStream (un flux buffersié qui a subi un filtre vers ton port com)
    4. ComStream + BufferStream + FilterStream + CompressStream (un flux bufferisé qui a subi un filtre et qui a été compressé vers ton port com)
    5. ComStream + BufferStream + CompressStream (un flux bufferisé qui a été compressé mais non filtré vers ton port com
    6. ComStream + CompressStream (un flux non bufferisé, non filtré mais compressé vers ton port com)
    7. ComStream + FilterStream + CompressStream (un flux non bufferisé, filtré et compressé vers ton port com)
    8. et combien d'autres, vu que j'ai gardé tout le temps le même ordre

    Chacune de ces classes devant présenter:
    • un nom différent
    • mais une interface similaire, faisant appel aux différents éléments qui la compose (dans l'ordre qui nous intéresse à ce moment là)

    Ce qui, de ton coté, va déjà poser quelques problèmes en termes de nommage. Mais bon: ton rôle, en tant que développeur d'une fonctionnalité étant de te casser la tête pour que le utilisateurs de cette fonctionnalité n'aient pas à le faire (et, de préférence, en leur évitant de faire des conneries), on pourrait encore estimer que c'est "normal"

    Cependant, il faut bien te dire que l'utilisateur de ta classe peut vouloir faire deux choses:

    Soit, il veut simplement créer une instance de ta classe et l'utiliser sans que tu lui ai laissé la possibilité de faire une connerie. S'il doit -- encore -- définir le filtre, le buffer et le mode de compression avant de pouvoir l'utiliser correctement, tu peux être sur qu'il va oublier de faire l'une de ces choses à un moment ou à un autre, et ce, de préférence toujours au pire moment qui soit car l'utilisateur est un imbécile distrait qui n'attend que l'occasion de faire une connerie.

    Et si l'utilisateur a déjà le choix entre sept classes qui font sensiblement la même chose (envoyer des données vers le port com), mais avec "quelque truc en plus ou en moins" en fonction de la classe qu'il aura choisi d'utiliser, c'est foutu car il ne saura pas laquelle choisir à la base (il se casse la tête).

    Soit il peut décider de spécialiser ta classe (mettons la classe ComStream + BufferStream + filterStream), par exemple, en modifiant le filtre "général" qui, dans son cas particulier ne correspond pas au filtre qu'il veut appliquer ... A moins qu'il ne veuille y ajouter la compression Ce serait dommage, car il se fait qu'il existe déjà une classe qui le fasse.

    Ce dont tu as besoin c'est d'une classe "de façade" qui mette d'office ComStream en jeu (vu que tous nos flux vont finir sur le port com) et d'une autre classe de façade pour chacun des éléments susceptibles d'être ajouté "à la demande" et qui viendraient potentiellement de bibliothèques différentes.

    De cette manière, l'utilisateur pourrait se contenter d'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
    Cmoncom comm;
    comm.add(compression)
              .add(buffer)
              .add(filter);
    /* ok, maintenant, j'ai un flux vers mon port com qui est buffersisé, filtré et compressé, je peux envoyer mes données */
     
    /* OU   -   OU   -   OU */
    Cmoncom comm2;
    comm2.add(buffer)
               .add(filter);
    /* ici, j'ai juste un flux bufferisé et filtré, mais c'est ce dont j'ai besoin */
    Car il est vrai que l'un des plus gros problèmes, surtout lorsque tu travailles avec plusieurs bibliothèques externes, sera toujours de faire en sorte que les interfaces des différentes fonctionnalités soient "compatibles" entre elles
    NB : j'appel "developeur final" celui qui vas utilisé la lib dans son code
    et "developpeur" celui qui ajoute des elements à la lib.
    tu as utilisé "utilisateur" qui peut etre confus : pour moi l'utilisateur est celui qui utilise le logiciel.
    Hé bien non, justement...

    Dis toi que tu n'es le développeur d'une fonctionnalité que le temps strictement nécessaire à en écrire / modifier / corriger le code et à t'assurer (au travers de tests unitaires, par exemple) qu'elle fonctionne correctement.

    Dés que tu estimes que ta fonctionnalité est "au point" et qu'elle fait ce que tu attends d'elle et que tu passes à "autre chose" (sans doute une autre fonctionnalité qui ... utilisera celle que tu viens de terminer), tu deviens ... un "simple" utilisateur lambda de la fonctionnalité qui a été mise au point.

    Ne fais surtout pas l'erreur de croire que, parce que c'est toi qui a développé la fonctionnalité que tu veux utiliser (peux-tu d'ailleurs garantir que ce sera toi ), tu puisse avoir un avantage quelconque sur n'importe quel autre personne (arrivée dans l'équipe hier matin) en terme d'utilisation de ta fonctionnalité.

    Cela peut être vrai dans les cinq minutes qui suivent la mise au point de la fonctionnalité. Mais, après quelques jours, semaines mois ou années, tu auras oublié toutes les décisions que tu as pu prendre lors du développement de cette fonctionnalité
    (vecu souvant) si tu as 10 streams en entree 3 en sortis....
    ca donne combien de variable a maintenir ?
    et quand tu as toutes ces variables à maintenir, quel est le risque de prendre la troisième lorsque tu voulais en réalité utiliser la première
    et tu as interet a BIEN les nommer !
    Nous sommes bien d'accord là dessus: tout devra être nommé "au milimètre près
    en plus tu devra fermer des wrappers dans l'ordre.
    Même pas... Du moins, pas forcément...

    Car, si tu applique correctement le DIP (dernier principe SOLID : principe de l'inversion des dépendances), et que tu t'arrange pour que tes wrappers reçoivent un flux en paramètre lors de leur travail, la seule chose que tu pourras fermer avec ton wrapper sera ... ton wrapper.

    Si, en plus, ton flux prend la responsabilité de maintenir les wrappers existant avec des pointeurs intelligents (std::unique_ptr en priorité), et que tu as 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
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    /* une classe de base servant de facade à l'utilisation de tous les flux issus de bibliothèques différentes */
    class Wrapper{
    public:
        Wrapper(Wrapper const &) = delete; // on ne peut pas le copier
        Wrapper & operator=(Wrapper & const &); // on ne peut pas l'assigner
        virtual ~Wrapper() = default; // il faut pouvoir détruire le wrapper spécifique
        virtual void execute(Cmoncom &) = 0; 
    protected:
         /* pour éviter que l'utilisateur n'essaye de créer une instance de wrapper sans précision */
        Wrapper() = default;
    };
    /* un wrapper pour la compression */
    class CompressWrapper : public Wrapper{
    public:
        CompressWrapper(); // s'assure d'initialiser correctement la bibliothèque de compression
       ~CompressWrapper(); // s'assure de fermer correctement le basard 
        void execute(Data & d)  override; // on  ne fait qu'utiliser la bibliothèque de compression
                                                              // lance une exception si ca ne fonctionne pas
    };
     
    /* un wrapper pour la bufferisation */
    class BufferWrapper : public Wrapper{
    public:
        BufferWrapper(); // s'assure d'initialiser correctement la bibliothèque de bufferisation
       ~BufferWrapper(); // s'assure de fermer correctement le basard 
        void execute(Data & d)  override; // on  ne fait qu'utiliser la bibliothèque de bufferisation
                                                              // lance une exception si ca ne fonctionne pas
    };
    class FilterWrapper : public Wrapper{
     
        FilterWrapper(); // s'assure d'initialiser correctement la bibliothèque de filtres
       ~FilterWrapper(); // s'assure de fermer correctement le basard 
        void execute(Data & d)  override; // on  ne fait qu'utiliser la bibliothèque de bufferisation
                                                              // lance une exception si ca ne fonctionne pas
    };
     
    Cmoncom{
    public:
        /* toujours la sémantique d'entité ;) */
       Cmoncom() = default;
       Cmoncom(CMoncom const &) = delete; // on ne peut pas copier un flux
       Cmoncom & operator(Cmoncom const &); // on ne peut pas assigner un flux à un autre
       ~Cmoncom() = default; // a-t-on vraiment une bonne raison de vouloir l'utiliser comme classe de base dans un héritage?
        void execute(){
            for(auto & wrap : wrappers_)
                wrap.get()->execute(*this);
            comm_.execute(*this):
        }
        void add(WrapperType t){ // une simple énumération pourrait suffir :D
             wrappers_.emplace_back(std::move(Factory::create(t)); // on utilise une fabrique pour créer les wrappers ;)
        }
        void remove(WrapperType t){
            /* si l'on n'a plus besoin d'un wrapper particulier, il "suffit" de le retirer :D */
        }
    private:
        Datas & datas_; // les données qui seront envoyées
        ComWrapper comm_; // le flux vers le port com (obligatoire, celui-ci)
        std::vector<std::unique_ptr<Wrapper>> wrappers_; // les autres flux (optionnels) qui seront utilisés
    }
    l'utilisateur se retrouve avec une seule classe dont il doit tenir compte: Cmoncom. Si il veut ajouter des traitements particulier, il n'a qu'à utiliser la valeur énumérée qui y correspond et n'a absolument rien d'autre à faire, et, quand il n'a plus besoin d'un traitement spécifique, il le supprime (tout en gardant la possibilité d'utiliser les autres)

    Il n'a même pas vraiment besoin de se rendre compte qu'il existe une classe CompressWrapper ou une classe BufferWrapper car elles ne servent ici qu'à nous fournir des services auxquels il peut accéder simplement ... en ayant indiqué qu'il souhaitait en profiter

    Bien sur, je n'ai présenté qu'une ébauche à peine réfléchie de la solution et elle nécessitera d'être largement améliorée. Cependant, le principe est déjà bel et bien présent
    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

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

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 628
    Points : 10 553
    Points
    10 553
    Par défaut
    Citation Envoyé par foxzoolm Voir le message
    si tu n'as que 1 seul stream dans ton programme...
    Tu n'as pas compris lorsqu'on fait 1 classe monolithique comme je le proposais, il faut s'assurer que le nombre de méthodes n'explosent pas (pour des raisons de maintenance, de couplage, de conception, ...)
    Et donc si tu n'as que 2 - 3 chiffrements, 1 - 3 compressions, et 1 bufférisassion, c'est jouable.

    parce que tu veux faire 1 bibliothèque modulable avec des mixins C++, mais dans la réalité quel sera le nombre de traitements supportés, le nombre d'algo de chiffrement et de compression supportés ?


    Citation Envoyé par foxzoolm Voir le message
    (vecu souvant) si tu as 10 streams en entree 3 en sortis....
    ca donne combien de variable a maintenir ?
    et tu as interet a BIEN les nommer !
    Pour le nommage, rien à cirer lorsque tu as + de 3 variables du même type, tu les mets dans 1 collection (1 tableau, 1 liste chaînée par exemple)
    Surtout, si ton code doit gérer plusieurs "streams".
    et ensuite tu cherches des problèmes de fermeture de "streams", cela donne 1 indication.

    Et ensuite, lorsque je parlais de "café" c'est peut-être là ton problème tu veux résoudre 1 problème de gestion mémoire avec des mixins/ décorateurs alors que ce sont 2 patrons qui ne sont pas là pour résoudre ces problèmes
    De plus, comme tu veux en faire 1 bibliothèque, tu te prends "les pieds dans le tapis", parce que très souvent modulable veut dire hiérarchie de classes.

    On ne connaît pas le contexte , mais si ton code doit gérer plusieurs "streams", pour gérer la mémoire, il faut mutualiser les traitements et faire des collections/ des bassins ("pool" en anglais) (<- les idées que j'avais émises dans mon précédent message, allaient dans ce sens)
    Et via des accesseurs/ mutateurs passer les streams à chacun.
    Par exemple, au démarrage, tu alloues 2 algo de compression c1 et c2, 1 algo de filtre f1 et 1 algo de bufférisassion b1. Et ensuite
    • si tu veux comprimer et filtrer 1 "stream" tu fais : c1.set(stream01); c1.compress(); f1.set(stream01); f1.exec();
    • si tu veux comprimer 1 "stream" tu fais : c1.set(stream02); c1.compress();


    Voire faire des traitements génériques et pouvoir changer à l'exécution leur comportement (par exemple, passer d'1 algo de compression à 1 algo de filtre) ... et ainsi fixer 1 nombre de traitements au démarrage adéquat, mais pouvoir les faire tous.


    Citation Envoyé par foxzoolm Voir le message
    en plus tu devra fermer des wrappers dans l'ordre.
    cela sent 1 manque de pointeur intelligent qui ferme automatiquement les ressources dans le destructeur ou alors 1 problème de conception.
    Lorsque dans tes traitements, tu parles d'ouvrir/ fermer au lieu d'initialiser/ nettoyer, cela donne 1 indication.

  20. #20
    Membre régulier
    Profil pro
    Inscrit en
    Août 2006
    Messages
    78
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 78
    Points : 122
    Points
    122
    Par défaut
    (vague sensation de me repeter)

    je comprend pas pouquoi tu bloque sur cmonstream ???

    hors celui ci c'est justement l'utlisateur/developpeur final qui defini/construit...
    IL NE LE DERRIVE PAS...
    il met dans son code a lui (il ne le derrive pas, il n'instance pas un cmonstream deja defini)

    il ecrit dans son code a lui
    (c'est SON CODE, DANS SON PROJET, ecrit avec ses petits doigts)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    class cmonstream:filestream, etc, etc //<--- il met CE QU'IL VEUX !!! dans l'ordre qu'il veux
    (je vais finir par faire des RLM moi hein)
    par contre, la clairement, il n'y a aucun moyen de verifier s'il ecrit de la merde...
    (ajoute une class qui n'est pas derrivé de modstream ou masterstream, ou s'il n'appel pas le constructor)

    les "utilisateurs" feront toujours des conneries..
    c'est pour ca qu'on touche de gros salaire et que nous avons la considération professionnel...

Discussions similaires

  1. Réponses: 1
    Dernier message: 12/07/2008, 12h25
  2. Réponses: 1
    Dernier message: 24/10/2006, 15h59
  3. [Tomcat]Tomcat ne trouve pas les driver mysql
    Par Lash3r dans le forum Tomcat et TomEE
    Réponses: 4
    Dernier message: 06/01/2006, 01h12
  4. [FEDORA] Je ne trouve pas les fichiers includesous Feodra core 3 ?
    Par sali dans le forum RedHat / CentOS / Fedora
    Réponses: 4
    Dernier message: 22/10/2005, 23h30
  5. execution d'un prog avec des fichiers multi-class
    Par Ice-B dans le forum Général Java
    Réponses: 2
    Dernier message: 05/08/2004, 11h43

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