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 :

Solution plus élégante possible ou pas ?


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Invité
    Invité(e)
    Par défaut Solution plus élégante possible ou pas ?
    Salut, alors je vais essayé d'expliqué ma solution.

    J'écris des données de tout type d'objets dans un fichier à l'aide de quelque classes et une macro qui associe un type de base à ses types dérivé en déclarant une structure qui hérite d'un sérialiseur qui contient un tuple avec un pointeur sur l'objet dérivé et un pointeur sur l'objet de base.

    Cette macro ajoute donc la déclaration de cette structure dans la classe de base ainsi qu'un objet de cette structure en variable membre à la classe de base. (j'appelle cet objet, la clé)

    Le sérialiseur contient plusieurs fonctions utiles :

    -Une pour rechercher le type réel de l'objet et appeler la fonction de sérialisation sur le type réel de l'objet en faisant une recherche à l'aide de typeid dans le tuple.
    -Une autre pour retourner l'index du pointeur sur le type réel de l'objet dans le tuple.
    -Et enfin, une autre pour pouvoir allouer le pointeur sur l'objet de base avec le type réel de l'objet à la lecture. (Pour connaître le type réel de l'objet j'écris juste l'id du type réel de base de l'objet dans un fichier)

    Le problème est que le compilateur n'a pas l'air d'aimer cela, bref, voici une solution que j'ai mis en place et qui marche avec l'option -fpermissive :

    Voici le sérialiseur qui contient le tuple et qui contient quelque fonctions/structure utilitaire d'allocation, de recherche d'index et de type ainsi que d'appel de la fonction sur le type réel de l'objet à l'exécution.

    Code cpp : 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
     
    #ifndef ODFAEG_SERIALIZATION
    #define ODFAEG_SERIALIZATION
    #include <iostream>
    #include <typeinfo>
    #include "tuple.h"
    #include <tuple>
    namespace odfaeg {
    template <int N, typename B, typename T, typename A>
    struct serializer {
        static void checkType (B* base, T tuple, A & ar) {
            if (std::get<N>(tuple) != nullptr && typeid(*base) == typeid(*std::get<N>(tuple))) {
                std::get<N>(tuple)->serialize(ar);
            }
            serializer<N-1, B, T, A>::checkType(base, tuple, ar);
        }
    };
    template <typename B, typename T, typename A>
    struct serializer <0, B, T, A> {
        static void checkType (B* base, T tuple, A & ar) {
            if (std::get<0>(tuple) != nullptr && typeid(*base) == typeid(*std::get<0>(tuple))) {
                std::get<0>(tuple)->serialize(ar);
            } else if (base != nullptr) {
                base->serialize(ar);
            }
     
        }
    };
    template <int N, typename B, typename T>
    struct sallocator {
        static B* instanciate (int index, T tuple) {
            if (N == index) {
                using D = typename std::remove_pointer<typename std::tuple_element<N, T>::type>::type;
                return new D();
            }
            sallocator<N-1, B, T>::instanciate(index, tuple);
        }
    };
    template <typename B, typename T>
    struct sallocator <0, B, T> {
        static B* instanciate (int index, T tuple) {
            using D = typename std::remove_pointer<typename std::tuple_element<0, T>::type>::type;
            return new D();
        }
    };
    template <int N, typename B, typename T>
    struct sindex {
        static int getIndex (B* base, T tuple) {
            if (std::get<N>(tuple) != nullptr && typeid(*base) == typeid(*std::get<N>(tuple))) {
                return N;
            }
            sindex<N-1, B, T>::getIndex(base, tuple);
        }
    };
    template <typename B, typename T>
    struct sindex <0, B, T> {
        static int getIndex (B* base, T tuple) {
            using D = typename std::remove_pointer<typename std::tuple_element<0, T>::type>::type;
            D* derived = new D();
            if (std::get<0>(tuple) != nullptr && typeid(*base) == typeid(*std::get<0>(tuple))) {
                return 0;
            }
            return -1;
        }
    };
     
    template <class B, class... D>
    class Serializer {
        public :
        Serializer() {
            baseObject = nullptr;
        }
        Serializer(B* baseObject) : baseObject(baseObject) {
            fillTuple(derivedClasses);
        }
        void setObject(B* baseObject) {
            this->baseObject = baseObject;
            fillTuple(derivedClasses);
        }
        template<int... S>
        void fillTuple(std::tuple<D*...> types) {
            fillTuple(typename helper::gens<sizeof...(D)>::type(), types);
        }
        template<int... S>
        void fillTuple(helper::seq<S...>, std::tuple<D*...> params) {
             derivedClasses = std::forward_as_tuple(dynamic_cast<typename std::remove_reference<decltype(std::get<S>(params))>::type> (baseObject)...);
        }
        template <typename Archive>
        void serialize(Archive & ar) {
            if (std::tuple_size<decltype(derivedClasses)>::value > 0)
                serializer<std::tuple_size<decltype(derivedClasses)>::value-1, B, decltype(derivedClasses), Archive>::checkType(baseObject, derivedClasses, ar);
            else
                baseObject->serialize(ar);
        }
        virtual void onSave() {
        }
        virtual void onLoad() {
        }
        B* sallocate(int index) {
            if (std::tuple_size<decltype(derivedClasses)>::value > 0)
                return sallocator<std::tuple_size<decltype(derivedClasses)>::value-1, B, decltype(derivedClasses)>::instanciate(index, derivedClasses);
     
        }
        int getIndex() {
            if (std::tuple_size<decltype(derivedClasses)>::value > 0)
                return sindex<std::tuple_size<decltype(derivedClasses)>::value-1, B, decltype(derivedClasses)>::getIndex(baseObject, derivedClasses);
            return -1;
        }
        private :
        B* baseObject;
        std::tuple<D*...> derivedClasses;
    };
    }
    #endif // SERIALIZATION

    Si le type statique de l'objet est le même que le type dynamique de l'objet, alors je retourne -1 comme index.

    Voici le contenu de mon autre fichier qui n'est rien d'autre qu'une macro qui va inclure le tout dans le fichier.h de la classe de base pour que je puisse avoir accès à la clé dans l'archive :

    Code cpp : 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
     
    #include "serialization.h"
    #ifndef SERIALIZATION_CPP
    #define SERIALIZATION_CPP
    #define REGISTER_KEY(KEY, TYPE, TYPES...)  \
    struct KEY : public  odfaeg::Serializer<TYPE, TYPES> { \
        public : \
        KEY () {} \
        void register_object (TYPE* object) { \
            setObject(object); \
        } \
        TYPE* allocate_object(int index) { \
            return sallocate(index); \
        } \
        int getTypeIndex () { \
            return odfaeg::Serializer<TYPE, TYPES>::getIndex(); \
        } \
        template <typename Archive> \
        void serialize_object (Archive & ar, int) { \
            odfaeg::Serializer<TYPE, TYPES>::serialize(ar); \
        } \
    }; \
    KEY key; \
    static TYPE* allocate (int index) { \
        static KEY aKey; \
        return aKey.allocate_object(index); \
    }
    #endif // SERIALIZATION_CPP

    Et enfin dans l'archive je n'ai plus qu'à accéder à la clé, afin d'écrire tout ça :

    Si l'objet possède un pointeur de fonction prenant une archive et un int, alors, cela veut dire que il est possible que l'objet soit polymoprhique, sinon, j'appelle directement la fonction serialize contenue dans la classe de l'objet. (J'ai deux fonction, une contenue dans la classe de l'objet et une autre contenue dans la définition de la clé), le int dans la fonction elle même ne sert donc à rien, il me sert juste à appliquer le principe de SFINAE.

    Code cpp : 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
     
    template <class O,
                  class... D,
                  class = typename std::enable_if<std::is_member_function_pointer<void(O::*)(OTextArchive&)>::value>::type,
                  class = typename std::enable_if<std::is_member_object_pointer<decltype(&O::key)>::value>::type,
                  class = typename std::enable_if<!std::is_same<O, std::string>::value>::type,
                  class = typename std::enable_if<!sizeof...(D)>::type>
    void operator() (O* object, D...) {
            std::map<unsigned long long int, unsigned long long int>::iterator it = adresses.find(reinterpret_cast<unsigned long long int>(object));
            if (it != adresses.end()) {
                buffer<<it->second<<" ";
            } else {
                int index = -1;
                std::pair<unsigned long long int, unsigned long long int> newAddress (reinterpret_cast<unsigned long long int>(object), nbSerialized);
                adresses.insert(newAddress);
                if (typeid(decltype(*object)) == typeid(*object)) {
                    std::cout<<"non polymoprhic version"<<std::endl;
                    buffer<<newAddress.second<<" ";
                    buffer<<index<<" ";
                    object->serialize(*this);
                } else {
                    std::cout<<"polymoprhic version"<<std::endl;
                    object->key.register_object(object);
                    index = object->key.getTypeIndex();
                    buffer<<newAddress.second<<" ";
                    buffer<<index<<" ";
                    object->key.serialize_object(*this, 0);
                }
                nbSerialized++;
            }
        }


    Je sauve donc l'index du type dynamique de l'objet. (-1 si le type dynamique est le même que le type statique)

    Cette solution marche extrêmement bien et me permet de définir ce que je veux sérialiser dans le fichier.h de la classe de base et même redéfinir ça dans le main si le fichier.h est contenu dans une librairie pour ne pas avoir à modifer le .h de la librairie si je rajoute des classes dérivées.

    Cependant il y a un petit problème à la lecture :

    Code cpp : 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
     
    void operator() (O** object, D...) {
     
            unsigned long long int id;
            int index;
            buffer>>id;
            buffer>>index;
            std::map<unsigned long long int, unsigned long long int>::iterator it = adresses.find(id);
            if (it != adresses.end()) {
                *object = reinterpret_cast<O*>(it->second);
            } else {
                if (index == -1) {
                     std::cout<<"non polymorphic version"<<std::endl;
                     *object = new O();
                    (*object)->serialize(*this);
                    std::pair<unsigned long long int, unsigned long long int> newAddress (id, reinterpret_cast<unsigned long long int>(object));
                    adresses.insert(newAddress);
                } else {
                    std::cout<<"polymorphic version"<<std::endl;
                    *object = O::allocate(index);
                    (*object)->key.register_object(*object);
                    (*object)->key.serialize_object(*this, 0);
                    std::pair<unsigned long long int, unsigned long long int> newAddress (id, reinterpret_cast<unsigned long long int>(object));
                    adresses.insert(newAddress);
                }
                nbDeserialized++;
            }
        }

    Que voici :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    ||=== Build: Debug in ODFAEG-DEMO (compiler: GNU GCC Compiler) ===|
    /home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|9|warning: "G2DEKEY" redefined [enabled by default]|
    /usr/local/include/odfaeg/Graphics/2D/entity.h|13|note: this is the location of the previous definition|
    /home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|36|warning: "/*" within comment [-Wcomment]|
    /usr/local/include/odfaeg/Core/archive.h||In instantiation of ‘void odfaeg::ITextArchive::operator()(O**, D ...) [with O = odfaeg::BoundingBox; D = {}; <template-parameter-1-3> = void; <template-parameter-1-4> = void; <template-parameter-1-5> = void; <template-parameter-1-6> = void]’:|
    /home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp|65|required from here|
    /usr/local/include/odfaeg/Core/archive.h|513|error: invalid conversion from ‘odfaeg::BoundingVolume*’ to ‘odfaeg::BoundingBox*’ [-fpermissive]|
    ||=== Build failed: 1 error(s), 4 warning(s) (0 minute(s), 5 second(s)) ===|
    J'ai donc rajouté l'option -fpermissive pour que gcc me foute la paix car de toute façon il n'ira jamais dans le else si le type dynamique est diffférent du type statiqque, mais je ne sais pas si c'est la meilleur solution.

  2. #2
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    J'ai rien compris

    Mais une simple surcharge de l'opérateur de flux << et >> ne suffit pas? Si c'est juste de la sérialisation tu te complique vraiment la vie.

    Sinon un manière élégante et plus complexe que les flux est de créer un Custom RTTI, je te laisse cherche sur Google

  3. #3
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Pour commencer, chaque warning devrait être corrigé...

    error: invalid conversion from ‘odfaeg::BoundingVolume*’ to ‘odfaeg::BoundingBox*’
    il te hurle (error) qu'un odfaeg::BoundingVolume* n'est pas un ‘odfaeg::BoundingBox*.
    C'est à dire qu'a priori, BoundingVolume n'hérite pas de BoundingBox
    C'est d'ailleurs très probablement le contraire.

    La solution est un down cast, qui je crois s'écrit avec std::dynamic_cast.
    Et c'est toujours une erreur.

    Le code utilisateur de ta sérialisation devrait pouvoir agir de la même manière qu'avec std::cout.
    Je m'explique:

    Si je veux écrire une classe Bidule sur cout, j'ai le code suivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class Bidule{}
     
    std::ostream& operator<<(std::ostream & os, Bidule const& that) {//avec un warning sur that unused, je sais...
        return os <<"un bidule quelconque";
    }
    Je suis obligé d'écrire une fonction supplémentaire ayant un nom et une signature précis.

    cela sous entends qu'on a dans <iostream> les choses suivantes:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class ostream{
    //...
    ostream& operator<<(int);
    ostream& operator<<(char);
    ostream& operator<<(double);
    ostream& operator<<(const void*);
    };
    (voir une spec)

    Tu pourrais faire pareil:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    namespace serializer{
    classe Serializer{
    Serializer& operator<<(int);
    Serializer& operator<<(char);
    Serializer& operator<<(double);
    Serializer& operator<<(const void*);
    };
    }
     
    namespace lib {
    class BoundingVolume{...};
    Serializer& operator<<(Serializer&, BoundingVolume const&);
    }
    et t'attendre à ce que ton utilisateur écrive ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    class Bidule : public BoundingVolume{...};
    Serializer& operator<<(Serializer&, Bidule const&);
     
    int main () {
        serializer::Serializer output;
        lib::BoundingVolume *box = new lib::BoundingBox(), *bidule = new Bidule(...);
        output<<*box << *bidule;
        return 0;
    }
    J'ai volontairement pris operator<<, parce qu'il y a une souplesse avantageuse: il peut être défini comme fonction membre ou comme fonction libre.
    Les fonctions membres pour les primitifs, car Serializer est le seul à savoir qu'en faire.
    Les fonctions libres pour les autres types, ce qui permet de les faire définir par l'utilisateur.


    Ce qu'il nous manque pour t'aider enfin, c'est une spécification du format d'archivage.
    Ca ressemble à la version fichier d'un vector< pair<string type, string serialform> >
    auquel cas, il te suffit de définir des fonctions qui produit les pair<string type, string serialform> correspondant à chaque type.

    Si je comprends bien, ton problème se résume à avoir une classe permettant de stocker des pointeurs de fonction de traduction.
    Quelque chose qui réponde au besoin logique suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    template <class C extends serializable> Archive& Archive::operator<<(C const&);
    il existe type_info, j'imagine que ceci est presque possible (je n'ai pas vraiment essayé)
    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 Archive {
    public:
    typedef Archive& (*serialize_t)(Archive&, serializable const&);
    private:
    map<type_info, serialize_t> helpers;
     
    public:
        void register(type_info const& type, serialize_t helper) {
            if ( ! /*validation de type*/ ) throw std::illegal_argument();
            helpers.emplace(type, helper);
        }
        Archive& operator<<(serializable const& that) {
            return helpers[/*je ne sais plus obtenir le type_info de that*/](*this, that);
        }
    };

  4. #4
    Membre actif
    Homme Profil pro
    Ingénieur
    Inscrit en
    Octobre 2006
    Messages
    48
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Transports

    Informations forums :
    Inscription : Octobre 2006
    Messages : 48
    Par défaut
    Citation Envoyé par Lolilolight Voir le message
    Salut, alors je vais essayé d'expliqué ma solution.

    J'écris des données de tout type d'objets dans un fichier à l'aide de quelque classes et une macro qui associe un type de base à ses types dérivé en déclarant une structure qui hérite d'un sérialiseur qui contient un tuple avec un pointeur sur l'objet dérivé et un pointeur sur l'objet de base.
    FAIL. Pourquoi faire simple quand on peut faire compliqué

  5. #5
    Invité
    Invité(e)
    Par défaut
    Salut.

    Finalement j'ai réussi à avoir un code qui compile, sans devoir rajouté d'option, je m'étais, en effet trompé et il fallait que je spécifie une surcharge de fonction supplémentaire.

    Je ne veux en effet ne pas passer par un système de "custom RTTI" qui compliquerai les choses.

    Je veux avoir un truc simple.

    Le problème des opérateurs << et >>, c'est qu'il ne peuvent prendre qu'un (ou deux si ce n'est pas une fonction membres) arguments supplémentaire, je ne peux pas utiliser SFINAE donc.

  6. #6
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Le problème des opérateurs << et >>, c'est qu'il ne peuvent prendre qu'un (ou deux si ce n'est pas une fonction membres) arguments supplémentaire, je ne peux pas utiliser SFINAE donc.
    Bas tu créer juste une class serialize, tu ajoutes des fonctions read write et tu les surcharges dans chaque classe enfant. je comprends pas pourquoi tu as fais si compliqué

  7. #7
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2014
    Messages
    345
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Moselle (Lorraine)

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

    Informations forums :
    Inscription : Juin 2014
    Messages : 345
    Par défaut
    Citation Envoyé par Lolilolight Voir le message
    Je veux avoir un truc simple.

  8. #8
    Invité
    Invité(e)
    Par défaut
    Salut.

    Je ne peux malheureusement pas surcharger une fonction template. :/

    Et puis j'ai voulu partir sur un système similaire à celui de boost pour des raisons de compatibilité.

  9. #9
    Invité
    Invité(e)
    Par défaut
    Ce code :

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    template <class C extends serializable> Archive& Archive::operator<<(C const&);

    Ne fonctionne que en java non ?

    Avec typeinfo je crains que cela ne soit pas possible, il faut appeler l'operateur<< sur le bon type, donc, je dois doit pouvoir stocker le bon type en compilation et le rechercher à l'exécution pour appeler la fonction de la bonne classe.

    Je trouve pas ças très pratique de devoir redéfinir l'operator<< pour tout les types primitifs.

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

Discussions similaires

  1. Obtenir un ensemble de solutions les plus différentes possibles
    Par karas.uchiwa dans le forum Algorithmes et structures de données
    Réponses: 2
    Dernier message: 30/03/2010, 12h28
  2. Réponses: 10
    Dernier message: 12/01/2006, 21h22
  3. [VBA]possible ou pas ? creer une image jpg a partir 7 jpg
    Par sakuraba dans le forum Général VBA
    Réponses: 5
    Dernier message: 03/01/2006, 10h45
  4. [Info]Solution CMS (Si possible JSP/Servlets)
    Par lolo le belge dans le forum Servlets/JSP
    Réponses: 6
    Dernier message: 16/12/2005, 22h55
  5. Permuter des valeurs, le plus rapidement possible?
    Par danje dans le forum Algorithmes et structures de données
    Réponses: 4
    Dernier message: 27/09/2005, 21h51

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