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 :

Pas de conversion implicite Objet => Objet& ? | Vtable obligatoire?


Sujet :

Langage C++

  1. #1
    Membre éclairé
    Avatar de Zenol
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2004
    Messages
    812
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2004
    Messages : 812
    Par défaut Pas de conversion implicite Objet => Objet& ? | Vtable obligatoire?
    Bonsoir,

    Je me remet doucement au C++ et je me pause quelques petites question.
    Je développe actuellement sou VC++ et j'ai essayer de compiler mon code avec G++(V4.3.2) sous debian.
    J'ai eu quelques surprises (VC++ est bien plus tolérant que g++), dont notamment un refus catégorique du compilateur(G++) de compiler quelque chose comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class C
    {
    public:
    //...
      setPosition(Position &pos);
    };
     
    //...
     
    int main(int argc, char *arv[])
    {
      //...
      aC.setPosition(Position(x, y, z));
      //...
    Le comportement que j'attendrais serais de créer une instance de Position dans la stack, et de passer la référence a la fonction setPosition, avant de détruire l'objet.
    Mais cela ne semble pas du gout de GCC...
    Quand au compilateur de VC++, je ne sais pas non plus si il fait bien cela ou d'autres bidouilles bien louche.

    Du coup, si je veux que la ligne précédente du main compile, je me retrouve a surcharger setPosition pour qu'il prenne un Position par copie.

    ---

    Deuxième petite question, j'ai voulus imposer a ma classe Point3D d'être "Computable", pour ce faire j'ai écris quelque chose dans ce genre :
    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
     
    template <class C, class T>
    class Computable
    {
      //...
      virtual C operator + (const C &c) const = 0;
      virtual C operator * (const T scal) const = 0;
      //...
    };
     
    template <class T>
    class Vector
    {
      virtual ~Vector() = 0;
    };
     
    template <class T>
    inline
    Vector::~Vector()
    {};
     
    //...
    template <class T>
    class Vector3D<T> : public Vector<T>, public Computable< Vector<T>, T>
    {
      //... Implementation des methodes de la forme
     
      //Retourne un nouveau vecteur, C = A + B
      Vector3D<T> operator + (const Vector3D<T> &vec) const;
     
      //Retourne un nouveau vecteur, somme du scalaire sur chacune des coords
      Vector3D<T> operator + (const T scal) const;
     
      //...
    };
    Donc, je me retrouve avec des Vector3D<float> dont je suis assurer de pouvoir les manipuler comme des nombres. (Produit, quotient, différence, somme, comparaison, etc...)

    Je ne sais pas si c'est très propre, j'y suis aller a l'instinct, mais d'après le debugger de VC++, Vect3D contient deux pointeurs vers une vtable, un pour chacun de ses parents.
    J'ai zapper quelque chose qui implique que Vector3D doit nécessairement avoir un pointeur sur la Vtable de Computable? (Parce qu'en fin de compte, il n'y a aucune méthode implémenté dans Computable, c'est un peu comme une "interface"...)

    Voila, ce sont mes questions existentiel de la journée.
    Mes articles Développez | Dernier article : Raytracer en haskell
    Network library : SedNL | Zenol's Blog : http://zenol.fr

    N'oubliez pas de consulter la FAQ et les cours et tutoriels.

  2. #2
    Membre chevronné Avatar de Lavock
    Profil pro
    Inscrit en
    Octobre 2009
    Messages
    560
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2009
    Messages : 560
    Par défaut
    1 - Je suis plutôt scandalisé que vc l'accepte >< !!!

    La norme dit qu'une reference (appelé maintenant lvalue reference) peut référencer... une lvalue.
    Une référence constante peut, elle référencé n'importe quoi.

    Déjà, il est sensé être plus rentable de prendre une copie. Dans se cas là, je crois que les compilateurs sont obligé par la norme de ne faire qu'une construction, et non une copie plus une construction.

    Ensuite, si tu veux garder des références, je te conseille de regarder du côté de C++0x et des rvalue reference.

    2 - Je ne connais pas VC, mais dans les ressource de 3DArchi tu vas trouver un gros article sur le mot clef "virtual" et tutti quanti. Regarde si ton bonheur ne s'y trouve pas !

  3. #3
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    En fait, une toute petite modification de ton code suffirait pour que tout rentre dans l'ordre (bon, il faut la répercuter à deux endroits ):

    "simplement" ajouter le mot clé const soit avant le terme Position, soit (de préférence) entre le terme position et le caractère "&".

    Si je dis qu'il faut le faire deux fois, c'est parce qu'il faut le faire... pour la déclaration de la fonction, et pour... son implémentation.

    En effet, le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    aC.setPosition(Position(x, y, z));
    va créer ce que l'on appelle une variable temporaire anonyme:

    Temporaire parce que... elle n'existe que pour la durée de l'exécution de... setPosition
    Anonyme parce que... on ne dispose d'aucun identifiant nous permettant... d'y accéder en dehors de l'appel de la fonction (comme, en plus, elle n'existe pas en dehors de l'appel de la fonction, c'est à peu près normal )

    Or, les auteurs de la norme ont estimé que, comme il n'y a aucun moyen de récupérer, dans la fonction appelante (main, selon ton exemple), les modifications qui pourraient être apportées durant la fonction appelée (setPosition, en l'occurrence), il n'y a... aucun avantage à... permettre à la fonction appelée (je parle toujours de setPosition) de modifier une telle variable temporaire anonyme.

    Du coup, ils ont décidé que l'on pourrait provoquer la création d'une variable temporaire anonyme en passant une référence sur un objet (il faut, ici, se rappeler qu'une référence est un alias d'un objet, et que, par conséquent, l'objet référencé doit... exister...) uniquement si... la référence en question est constante.

    Ainsi donc, en modifiant ta classe en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class C
    {
    public:
    //...
      void setPosition(Position const & pos);
    };
    et, fatalement, en modifiant, en modifiant l'implémentation de la fonction en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void C::setPosition(Position const & pos)
    {
        /*... ce qui doit être fait */
    }
    tu respectera la norme, et tu n'aura plus de problème à la compilation

    Ceci étant dit, j'ai personnellement horreur des mutateurs, surtout s'il s'exposent trop ostensiblement à coup de setMachinChose, et que je suis très attentif à la sémantique des différents identifiants utilisés.

    Au départ du code que tu nous montre, on peut, effectivement, estimer que:
    • la classe C devrait avoir une position de départ... Il serait pas mal d'envisager de mettre RAII en oeuvre
    • Une instance de la classe C est, visiblement, susceptible de se déplacer... Peut être faudrait-il se poser la question de la manière dont elle le fait. Est-ce:
      • vers des positions "relatives" (à la position actuelle)
      • vers des position "absolues"
    Selon la réponse que tu donnera à cette deuxième question, peut être serait il intéressant d'envisager l'utilisation d'une fonction move() (même si on ne fait que renommer setPositon), parce qu'elle est plus expressive de ce qui se fait
    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
    Membre éclairé
    Avatar de Zenol
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2004
    Messages
    812
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2004
    Messages : 812
    Par défaut
    Citation Envoyé par Lavock
    1 - Je suis plutôt scandalisé que vc l'accepte >< !!!

    La norme dit qu'une reference (appelé maintenant lvalue reference) peut référencer... une lvalue.
    Une référence constante peut, elle référencé n'importe quoi.

    Déjà, il est sensé être plus rentable de prendre une copie. Dans se cas là, je crois que les compilateurs sont obligé par la norme de ne faire qu'une construction, et non une copie plus une construction.

    Ensuite, si tu veux garder des références, je te conseille de regarder du côté de C++0x et des rvalue reference.

    2 - Je ne connais pas VC, mais dans les ressource de 3DArchi tu vas trouver un gros article sur le mot clef "virtual" et tutti quanti. Regarde si ton bonheur ne s'y trouve pas !
    1 - Ah bah, je ne savais pas! Si la norme le dis, la norme est renne, j'obtempère sur le champ.
    2 - Ok je vais aller voire ca.

    Citation Envoyé par koala01 Voir le message
    Salut,

    En fait, une toute petite modification de ton code suffirait pour que tout rentre dans l'ordre (bon, il faut la répercuter à deux endroits ):

    "simplement" ajouter le mot clé const soit avant le terme Position, soit (de préférence) entre le terme position et le caractère "&".

    Si je dis qu'il faut le faire deux fois, c'est parce qu'il faut le faire... pour la déclaration de la fonction, et pour... son implémentation.

    En effet, le code ... va créer ce que l'on appelle une variable temporaire anonyme:

    Temporaire parce que... elle n'existe que pour la durée de l'exécution de... setPosition
    Anonyme parce que... on ne dispose d'aucun identifiant nous permettant... d'y accéder en dehors de l'appel de la fonction (comme, en plus, elle n'existe pas en dehors de l'appel de la fonction, c'est à peu près normal )

    Or, les auteurs de la norme ont estimé que, aucun moyen de récupérer, dans la fonction appelante (main, selon ton exemple), les modifications qui pourraient être apportée durant la fonction appelée (setPosition, en l'occurence), il n'y a... aucun avantage à... permettre à la fonction appelée (je parle toujours de setPosition) de modifier une telle variable temporaire anonyme.

    Du coup, ils ont décidé que l'on pourrait provoquer la création d'une variable temporaire anonyme en passant une référence sur un objet (il faut, ici, se rappeler qu'une référence est un alias d'un objet, et que, par conséquent, l'objet référencé doit... exister...) uniquement si... la référnce en question est constante.

    Ceci étant dit, j'ai personnellement horreur des mutateurs, surtout s'il s'exposent trop ostensiblement à coup de setMachinChose, et que je suis très attentif à la sémantique des différents identifiants utilisés.

    Au départ du code que tu nous montre, on peut, effectivement, estimer que:
    • la classe C devrait avoir une position de départ... Il serait pas mal d'envisager de mettre RAII en oeuvre
    • Une instance de la classe C est, visiblement, susceptible de se déplacer... Peut être faudrait-il se poser la question de la manière dont elle le fait. Est-ce:
      • vers des positions "relatives" (à la position actuelle)
      • vers des position "absolues"
    Selon la réponse que tu donnera à cette deuxième question, peut être serait il intéressant d'envisager l'utilisation d'une fonction move() (même si on ne fait que renommer setPositon), parce qu'elle est plus expressive de ce qui se fait
    Merci pour ces précision sur les variables anonyme. C'est effectivement ce comportement que je recherchais, pour pouvoir au choix utiliser un objet existant ou bien un objet qui ne serait crée que le temps de l'appelle, et donc être assuré de l'absence de copie superflue.

    Sinon, pour répondre a tes questions :
    • la classe C devrait avoir une position de départ... Il serait pas mal d'envisager de mettre RAII en oeuvre

      => Enfaite, le constructeur est déjà un peux prit par d'autres choses pour les classes filles, et si je ne veux pas me retrouver avec deux lignes de paramètre a la construction, je vais me contenter de dire que tout C est positionné à l'origine. (Si j'ai bien comprit ce que tu voulais dire par RAII ^^')
    • Une instance de la classe C est, visiblement, susceptible de se déplacer...

      => Hum, pas vraiment enfaite. Elle a une place définie, mais on peut décider que finalement ca ne nous plait pas et donc d'en définir une autre.
      Bien sur, dans un second temps on peut envisager qu'effectivement l'objet se déplace. Mais dans ce contexte précis (Qui vraisemblablement finiras par arriver... du moins j'aimerais bien ^^) on considèreras qu'un intervalle de temps est composé de point discret et en chacun de ces point, tout les C on des positions précise fixé. (Et de plus une orientations, et des caractéristiques, propres à tout C et objet héritant de C)
    Mes articles Développez | Dernier article : Raytracer en haskell
    Network library : SedNL | Zenol's Blog : http://zenol.fr

    N'oubliez pas de consulter la FAQ et les cours et tutoriels.

  5. #5
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Citation Envoyé par Zenol Voir le message
    J'ai eu quelques surprises (VC++ est bien plus tolérant que g++)
    C'est un peu exagéré. Sur un tel code, VC++ avertit par un warning très explicite qu'il est en train d'utiliser une extension non standard "warning C4239: nonstandard extension used : 'argument' : conversion from 'Position' to 'Position &'"

  6. #6
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par Zenol Voir le message
    • la classe C devrait avoir une position de départ... Il serait pas mal d'envisager de mettre RAII en oeuvre

      => Enfaite, le constructeur est déjà un peux prit par d'autres choses pour les classes filles, et si je ne veux pas me retrouver avec deux lignes de paramètre a la construction, je vais me contenter de dire que tout C est positionné à l'origine. (Si j'ai bien comprit ce que tu voulais dire par RAII ^^')
    Le constructeur de la classe mère est pris par... des arguments nécessaires aux classe fille

    Tu n'a pas un peu l'impression que ce devrait etre le contraire

    De même, tu n'as pas un peut l'impression que tu en demande... peut être un peu trop à ta classe mère
    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

  7. #7
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Au fait, cette entrée de la FAQ explique ce que l'on entend par RAII.

    Ce concept tend à veiller à ce que, quel que soit l'objet créé, il le soit de manière à être directement utilisable et qu'il présente, autant que faire se peut, des attributs correspondant à un "état primitif" intéressant dans le contexte dans lequel l'objet est créé.

    Le principe est à mettre en parallèle avec son homologue concernant la destruction que nous pourrions nommer (s'il était officiellement reconnu) DIRR (Destruction Is Ressource Release) et qui a pour but de veiller, lors de la destruction d'un objets, à ce que toutes les ressources "accaparées" par l'objet soient... correctement libérées.
    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

  8. #8
    Membre éclairé
    Avatar de Zenol
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2004
    Messages
    812
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2004
    Messages : 812
    Par défaut
    Ah oui, j'avais zapper le warning. (Je préfère quand le compilateur me gueule l'erreur plutôt que lorsqu'il me la murmure. (Je peut pas non plus lui dire de s'arrêter au moindre warning, car pour faire du debug j'ai tendance a abuser de ces derniers ^^')

    Citation Envoyé par koala01 Voir le message
    Le constructeur de la classe mère est pris par... des arguments nécessaires aux classe fille
    Non, mais elle est abstraite. Je ne vois pas en quoi pouvoir affecter une position a la classe mère via son constructeur peut être utile, puisque ce constructeur si il existe ne seras appeler que par le constructeur de la classe fille.
    Citation Envoyé par koala01 Voir le message
    Tu n'a pas un peu l'impression que ce devrait être le contraire

    De même, tu n'as pas un peut l'impression que tu en demande... peut être un peu trop à ta classe mère
    Ma classe mère définie ce qu'est et ce que doit avoir toute classe fille, qui implémente ses méthodes.

    (Ma classe mère = Un objet dans l'espace, concepts abstrait. Classe fille : Un objet particulier dans l'espace, comme un chien, une voiture, une bouche d'incendie... Quelque chose qui existe et se comporte, et bien sur a une position.)

    Donc non je n'ai pas l'impression qu'elle fasse trop de chose. Enfaite, elle ne fait pas grand chose a part lister des méthodes virtuels pure, contenir des variables protected et définir les accesseur/mutateurs de ces variables. (Enfaite pour rendre le tout plus lisible elle hérite de deux classes, "Positionable" et "Orientable" qui elles ont leur destructeur non virtuel protégé.)

    Sinon, pour 2), j'ai lut le document en question (très intéressent au passage, si bien que je me retrouve avec une question supplémentaire ^^') mais je n'ai rien trouver qui puisse résoudre mon problème.
    Au pire je peux me décide a passer le destructeur private pour éviter que l'on tente de détruire des objets Computable&|*, mais je ne vois pas trop comment dire au compilo que ca ne sert a rien de faire un pointeur de vtable pour les classes qui hérite. Je me demande si il n'y aurais pas moyen de faire exactement la même chose sans virtual avec uniquement des template...

    Enfin, une petite question sur les vtable et les pointeurs.
    Si j'ai
    class A
    class B
    class C : public A, public B
    Out toute ces classe contienne des méthodes virtuel.
    Supposon que les pointeurs sur vtable se situent au debut des objets.
    Soit this, une instance de C, on a alors :
    (void*)(this) un pointeur sur vtable de B
    (void*)((long int*)this + 4) un pointeur sur vtable de A.

    La question que je me pose est : comment le compilo gère un de type A* sur un membre de C et un pointeur de type B* sur un membre de C?
    Si pour A* la réponse est simple (Il suffit de considérer (int*)this+4 et lors d'un downcasting on calcule this-4) la réponse n'est pas si évidente pour B...
    La solution qui me viens a l'esprit c'est "on swap *(int*)(this) et *((int*)this + 4) puis on calcule this+4" et l'opération réciproque pour du downcasting.
    Qu'en est-il de la réalité? (Avec GCC par exemple)

    Edit : J'ai déjà lut la FAQ sur RAII, mais justement je vois pas vraiment ce que ca viens faire dans mon cas. Ici la ressource est une Position, qui possède son constructeur, qui s'initialise toute seule avec les bonnes valeurs, et qui se détruit toute seul. Je pourrais ajouter les paramètre pour l'initialiser dans le constructeur de Object, mais c'est inutile car on ne peut pas créer un ClasseMere, mais seulement des ClasseFille. Si je voulais donc faire remonter l'info jusqu'au constructeur de Position, il me faudrait traverser toute une hiérarchie de constructeur et bien sur ajouter ces paramètres au constructeur de chacune de mes classes filles (Et il vas y en avoir beaucoup des classes filles!)
    Mes articles Développez | Dernier article : Raytracer en haskell
    Network library : SedNL | Zenol's Blog : http://zenol.fr

    N'oubliez pas de consulter la FAQ et les cours et tutoriels.

  9. #9
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Salut,
    2 raisons indépendantes pour avoir un vpointer :
    1/La présence de fonction virtuelles dans la classe de base, le vpointeur sert à désigner la vtable. Et ce, même si toutes les fonctions sont virtuelles pures. Car pur ou pas, ta classe de base peut avoir plusieurs classes dérivées et/ou implémenter des fonctions virtuelles pures. Donc, le besoin de la vtable est là. Et c'est ton cas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template <class C, class T>
    class Computable
    {
      //...
      virtual C operator + (const C &c) const = 0;
      virtual C operator * (const T scal) const = 0;
      //...
    };
    2/un vpointeur s'il y a un héritage virtuel mais pour des raisons toutes différentes (problématique de gestion d'adresse). Mais ce n'est pas ton cas.

    Pour fait sans héritage, j'ai tendance à penser au CRTP :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <class C, class T>
    class Computable
    {
      //...
      C operator + (const C &c) const
      {
             return static_cast<C&>(*this).operator +(c);
      }
      //...
    };

  10. #10
    Membre chevronné Avatar de Lavock
    Profil pro
    Inscrit en
    Octobre 2009
    Messages
    560
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2009
    Messages : 560
    Par défaut
    C'est parce que il n'y as pas que les vtable, il y a aussi les VTT. Voilà un article qui complète très bien celui de 3DArchi, notemment la partie Comment ça marche ?.

  11. #11
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par Lavock Voir le message
    C'est parce que il n'y as pas que les vtable, il y a aussi les VTT. Voilà un article qui complète très bien celui de 3DArchi, notemment la partie Comment ça marche ?.
    Sauf que dans son cas, c'est uniquement lié à la présence de fonctions virtuelles pures de sa classe de base. Il n'a pas d'héritage virtuel

  12. #12
    Membre chevronné Avatar de Lavock
    Profil pro
    Inscrit en
    Octobre 2009
    Messages
    560
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2009
    Messages : 560
    Par défaut
    J'avais mal lu ça nouvelle question, j'ai imaginé un héritage en diamand... Mais c'est à ça que j'ai essayé de répondre...

    En gros, c'est à ça que serve les vTables : lorsque tu pointe avec A* alors que tu as un C*, en fait, tu pointe la vtable de A dans C. Idem pour B. Ce sont les opérations implicite du up-cast : dans
    En réalité, tu fais un peu b = &(valueOfC.vTableB). Les champs direct de B se trouvant après la vtable.

    Il me semblait que tout ça été dit dans l'article d'arch... Mais après verif, il parle effectivement de ce que contient la table, pas de comment elle est stockée (peut être devrait-tu rajouter ceci ?)

    Du coup, je te renvoie au lien donné plus haut... Tout ça y est expliqué.

  13. #13
    Membre éclairé
    Avatar de Zenol
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2004
    Messages
    812
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2004
    Messages : 812
    Par défaut
    Citation Envoyé par Lavock Voir le message
    J'avais mal lu ça nouvelle question, j'ai imaginé un héritage en diamand... Mais c'est à ça que j'ai essayé de répondre...

    En gros, c'est à ça que serve les vTables : lorsque tu pointe avec A* alors que tu as un C*, en fait, tu pointe la vtable de A dans C. Idem pour B. Ce sont les opérations implicite du up-cast : dans
    En réalité, tu fais un peu b = &(valueOfC.vTableB). Les champs direct de B se trouvant après la vtable.

    Il me semblait que tout ça été dit dans l'article d'arch... Mais après verif, il parle effectivement de ce que contient la table, pas de comment elle est stockée (peut être devrait-tu rajouter ceci ?)

    Du coup, je te renvoie au lien donné plus haut... Tout ça y est expliqué.
    Enfaite, ce que je n'avais pas vu c'est que l'on a :
    struct C {
    vtable A;
    champs de A;
    vtable B;
    champs de B;
    }
    ce qui ne pose plus le moindre problème.
    J'ai compris ca grâce à ton lien donc merci beaucoup, c'est plus claire

    De plus, les infos sur l'héritage virtuel dans les cas héritage en diamant sont particulièrement intéressante.

    Citation Envoyé par 3DArchi Voir le message
    Salut,
    2 raisons indépendantes pour avoir un vpointer :
    1/La présence de fonction virtuelles dans la classe de base, le vpointeur sert à désigner la vtable. Et ce, même si toutes les fonctions sont virtuelles pures. Car pur ou pas, ta classe de base peut avoir plusieurs classes dérivées et/ou implémenter des fonctions virtuelles pures. Donc, le besoin de la vtable est là. Et c'est ton cas :
    ...
    2/un vpointeur s'il y a un héritage virtuel mais pour des raisons toutes différentes (problématique de gestion d'adresse). Mais ce n'est pas ton cas.

    Pour fait sans héritage, j'ai tendance à penser au CRTP
    J'avais lut en diagonale ce passage de la FAQ sans tilter que c'était exactement ce que je cherchais à faire.
    Du coup, c'est réglé, merci
    Mes articles Développez | Dernier article : Raytracer en haskell
    Network library : SedNL | Zenol's Blog : http://zenol.fr

    N'oubliez pas de consulter la FAQ et les cours et tutoriels.

  14. #14
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par Zenol Voir le message
    Non, mais elle est abstraite. Je ne vois pas en quoi pouvoir affecter une position a la classe mère via son constructeur peut être utile, puisque ce constructeur si il existe ne seras appeler que par le constructeur de la classe fille.
    Classe abstraite ou non, si un attribut est commun à l'ensemble des classes dérivées, il vaut très clairement la peine d'être défini... dans la classe mère...

    Et les fonctions (pas forcément virtuelles car n'étant pas pas forcément destinées à être redéfinies) qu'implique cet attribut méritent, elles-aussi, de se trouver dans la classe mère.

    Tout cela peut être considéré comme "le tronc commun" reliant l'ensemble des classes dérivées entre elles

    Et si cela te permet, parce qu'il est possible de donner une implémentation commune à l'ensemble des classes filles, de transformer une fonction virtuelle pure en une fonction non virtuelle ou en une fonction "simplement" virtuelle (mais non pure), c'est tout bénef

    Il ne faut pas oublier que, si une classe mère est abstraite du fait de la présence defonction(s) virtuelle(s) pure(s), tu es obligé de fournir une implémentation pour l'ensemble des fonctions virtuelles pures pour toutes les classes concrètes...

    Il serait dommage de devoir faire un "copier / coller" d'un comportement donné dans toutes les classes filles, pour obtenir une classe concrète alors que le seul fait de rajouter un attribut à la classe mère suffirait à... permettre aux classes filles de partager le dit comportement


    (Ma classe mère = Un objet dans l'espace, concepts abstrait. Classe fille : Un objet particulier dans l'espace, comme un chien, une voiture, une bouche d'incendie... Quelque chose qui existe et se comporte, et bien sur a une position.)
    Cela n'empêche que les comportements qui consistent à gérer la position d'un chien, d'une voiture, d'une bouche d'incendie dans l'espace sont, aux minimum, communs à toute instance d'objet dans l'espace.

    Si ta classe mère s'appelle "localisable", tu peux donc, selon ta conception, créer une collection d'objets localisables dans l'espace qui contient le chien occupé à pisser contre la bouche d'incendie, devant laquelle est garée la voiture et qui se trouve à trois maisons du du policier en faction, la voiture, la bouche d'incendie, le chien, le policier en faction et les trois maisons cohabitant dans... la même collection d'objets localisables.

    Si ce n'est pas ce que tu veux faire (parce que je peux tirer des conclusions erronées sur base de tes interventions), l'héritage n'a pas lieu d'être
    Donc non je n'ai pas l'impression qu'elle fasse trop de chose. Enfaite, elle ne fait pas grand chose a part lister des méthodes virtuels pure, contenir des variables protected et définir les accesseur/mutateurs de ces variables. (Enfaite pour rendre le tout plus lisible elle hérite de deux classes, "Positionable" et "Orientable" qui elles ont leur destructeur non virtuel protégé.)
    Disons que tu t'étais surtout mal exprimé avec ton
    => Enfaite, le constructeur est déjà un peux prit par d'autres choses pour les classes filles, et si je ne veux pas me retrouver avec deux lignes de paramètre a la construction, je vais me contenter de dire que tout C est positionné à l'origine. (Si j'ai bien comprit ce que tu voulais dire par RAII ^^')
    qui sous entendais que le constructeur de la classe mère prenait déjà des arguments nécessaires... au constructeur des classes filles.

    Et tu reconnaitra que, ainsi exprimé, ce n'est pas vraiment logique

    Ceci dit, j'ai réellement tendance à fuir comme la peste les constructeurs qui demandent trop d'argument (au dela de trois ou quatre, je commence déjà réellement à me poser des questions )

    *Peut être* pourrais tu envisager de créer des structures regroupant les différentes données nécessaires aux classes fille en fonction de leur utilité, ce qui *pourrait* te permettre de limiter très fortement le nombre d'arguments à transmettre

    De même, tu pourrais peut être, si l'idée est de passer un nombre plus ou moins déterminé d'objets de types identiques ou à défaut de types compatibles (par exemple, des pointeurs vers des objets localisables, pour reprendre ce qui a été dit jusqu'à présent), envisager de passer des "ranges" représentés par un itérateur de début et un itérateur de fin.

    Voillà déjà deux idées relativement simples à mettre en oeuvre pour limiter le nombre d'arguments qu'il te faut passer aux constructeurs des différentes classes fille, mais il y en a sans doute d'autres, et qui seront très certainement de nature à t'éviter bien du soucis
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  15. #15
    Membre éclairé
    Avatar de Zenol
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2004
    Messages
    812
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2004
    Messages : 812
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Classe abstraite ou non, si un attribut est commun à l'ensemble des classes dérivées, il vaut très clairement la peine d'être défini... dans la classe mère...

    Et les fonctions (pas forcément virtuelles car n'étant pas pas forcément destinées à être redéfinies) qu'implique cet attribut méritent, elles-aussi, de se trouver dans la classe mère.

    Tout cela peut être considéré comme "le tronc commun" reliant l'ensemble des classes dérivées entre elles

    Et si cela te permet, parce qu'il est possible de donner une implémentation commune à l'ensemble des classes filles, de transformer une fonction virtuelle pure en une fonction non virtuelle ou en une fonction "simplement" virtuelle (mais non pure), c'est tout bénef

    Il ne faut pas oublier que, si une classe mère est abstraite du fait de la présence defonction(s) virtuelle(s) pure(s), tu es obligé de fournir une implémentation pour l'ensemble des fonctions virtuelles pures pour toutes les classes concrètes...

    Il serait dommage de devoir faire un "copier / coller" d'un comportement donné dans toutes les classes filles, pour obtenir une classe concrète alors que le seul fait de rajouter un attribut à la classe mère suffirait à... permettre aux classes filles de partager le dit comportement


    Cela n'empêche que les comportements qui consistent à gérer la position d'un chien, d'une voiture, d'une bouche d'incendie dans l'espace sont, aux minimum, communs à toute instance d'objet dans l'espace.

    Si ta classe mère s'appelle "localisable", tu peux donc, selon ta conception, créer une collection d'objets localisables dans l'espace qui contient le chien occupé à pisser contre la bouche d'incendie, devant laquelle est garée la voiture et qui se trouve à trois maisons du du policier en faction, la voiture, la bouche d'incendie, le chien, le policier en faction et les trois maisons cohabitant dans... la même collection d'objets localisables.

    Si ce n'est pas ce que tu veux faire (parce que je peux tirer des conclusions erronées sur base de tes interventions), l'héritage n'a pas lieu d'être
    Disons que tu t'étais surtout mal exprimé avec ton
    qui sous entendais que le constructeur de la classe mère prenait déjà des arguments nécessaires... au constructeur des classes filles.

    Et tu reconnaitra que, ainsi exprimé, ce n'est pas vraiment logique

    Ceci dit, j'ai réellement tendance à fuir comme la peste les constructeurs qui demandent trop d'argument (au dela de trois ou quatre, je commence déjà réellement à me poser des questions )

    *Peut être* pourrais tu envisager de créer des structures regroupant les différentes données nécessaires aux classes fille en fonction de leur utilité, ce qui *pourrait* te permettre de limiter très fortement le nombre d'arguments à transmettre

    De même, tu pourrais peut être, si l'idée est de passer un nombre plus ou moins déterminé d'objets de types identiques ou à défaut de types compatibles (par exemple, des pointeurs vers des objets localisables, pour reprendre ce qui a été dit jusqu'à présent), envisager de passer des "ranges" représentés par un itérateur de début et un itérateur de fin.

    Voillà déjà deux idées relativement simples à mettre en oeuvre pour limiter le nombre d'arguments qu'il te faut passer aux constructeurs des différentes classes fille, mais il y en a sans doute d'autres, et qui seront très certainement de nature à t'éviter bien du soucis
    Enfaite, je détaille un peux plus la structure de mon projet :

    Localisable contient : Point3D position(x, y, z), les accesseurs/mutateur de position. Le constructeur est privé et non virtuel. De cette façon, on ne PEUX(et de DOIS pas) supprimer un Localisable*. (Le bute est clairement d'empêcher la manipulation de Localisable*)
    De même pour Orientable.

    Ensuite, j'ai Object : public Localisable, pulic Orientable.

    Enfin, je manipule une collection de Object* (Sphere, Ovoide, etc...).
    Les seuls fonction virtuel pure sont le calcule d'une intersection entre cette objet et un couple (Point,Vecteur) dans l'espace, le calcule de la normal à partir de la dite intersection, etc... Bref, dans tout les cas ceci DOIT être redéfinit pour chaque objet est lui est propre.

    Ce qui dois figurer dans le constructeur des classes fille, celon moi, c'est simplement le rayon de la sphère, les rayons de l'ovoïde, l'angle du cône de révolution...

    La couleur, la position, l'orientation, et tout modificateur seront ensuite setter par des méthodes fournie par Object (Via Localisable, Orientable, etc...)

    Donc, j'ai plutôt impression de respecter ce que tu a dis, mais si je me trompe dis moi
    Mes articles Développez | Dernier article : Raytracer en haskell
    Network library : SedNL | Zenol's Blog : http://zenol.fr

    N'oubliez pas de consulter la FAQ et les cours et tutoriels.

  16. #16
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Salut,
    Citation Envoyé par Zenol Voir le message
    Le constructeur est privé et non virtuel. De cette façon, on ne PEUX(et de DOIS pas) supprimer un Localisable*. (Le bute est clairement d'empêcher la manipulation de Localisable*)
    De même pour Orientable.
    (j'imagine que tu voulais parler du destructeur).
    Si le but est d'empêcher la manipulation de Localisable*, mettre le destructeur privé ne suffit pas. Dire 'je ne veux pas que Objet soit manipulé comme Localisable*', cela revient pour moi à dire que Localisable est un détail d'implémentation de Objet. Donc l'héritage devrait être privé.
    C'est d'ailleurs le seul moyen qui empêche un Objet d'être utilisé comme un Localisable. Le destructeur privé interdit juste la destruction d'un pointeur Localisable.
    Viendrais-tu du Java ? J'ai l'impression que tu assembles ta classe en indiquant quelles interfaces elle doit avoir (localisable, orientable, computable, etc.) ?
    J'ai quand même l'impression des éléments que tu présentes que ton héritage devrait être privé.

  17. #17
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par Zenol Voir le message
    Enfaite, je détaille un peux plus la structure de mon projet :

    Localisable contient : Point3D position(x, y, z), les accesseurs/mutateur de position. Le constructeur est privé et non virtuel. De cette façon, on ne PEUX(et de DOIS pas) supprimer un Localisable*. (Le bute est clairement d'empêcher la manipulation de Localisable*)
    De même pour Orientable.

    Ensuite, j'ai Object : public Localisable, pulic Orientable.

    Enfin, je manipule une collection de Object* (Sphere, Ovoide, etc...).
    Les seuls fonction virtuel pure sont le calcule d'une intersection entre cette objet et un couple (Point,Vecteur) dans l'espace, le calcule de la normal à partir de la dite intersection, etc... Bref, dans tout les cas ceci DOIT être redéfinit pour chaque objet est lui est propre.

    Ce qui dois figurer dans le constructeur des classes fille, celon moi, c'est simplement le rayon de la sphère, les rayons de l'ovoïde, l'angle du cône de révolution...

    La couleur, la position, l'orientation, et tout modificateur seront ensuite setter par des méthodes fournie par Object (Via Localisable, Orientable, etc...)

    Donc, j'ai plutôt impression de respecter ce que tu a dis, mais si je me trompe dis moi
    J'ai en fait l'impression qu'il y a deux problème à ta manière de travailler:

    Le premier a trait au RAII qui a pour objectif de fournir des objets directement utilisables

    Si tu donne directement sa position de départ à un de tes objets lorsque tu le crée, tu peux te contenter de le construire, puis de vérifier si, par exemple, il n'entre pas en collision avec un autre.

    Tu pourrais même envisager de le vérifier au moment de sa création.

    Par contre, Si tu ne donnes pas la position de départ, tes différents objets seront construits en ayant... un position par défaut (0,0,0 ) et entreront, fatelement, en collision avec... tout objet construit mais pour lequel tu n'aurait pas encore précisé la position.

    Ce que je veux dire par là, c'est qu'il est beaucoup plus facile (et sécurisant) de faire un
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    Object *ov = new Ovoide(/* parametre utiles à ovoïdes*/, position(15,42,53));
    qui place directement l'objet... là où il doit être que de faire un
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Object *ov = new Ovoide(/* paramètres utiles à ovoïdes */);
    ov->setPosition(position(15,42,53));
    parce que, si tu oublie la deuxième ligne, ton ovoïde se trouvera... à une position qui n'est pas la sienne.

    Le deuxième problème que j'y vois, c'est que la position d'un objet, quel qu'il soit, est une... propriété intrinsèque de l'objet.

    Comme je viens de le préciser, il me semble "logique" de faire en sorte que cette position soit... définie dés la création de l'objet

    De plus, à bien y réfléchir, certains objets sont (éventuellement) "déplaçables", et d'autres ne le sont... (sans doute) pas.

    Modifier la position d'objets qui ne sont pas déplaçable présente déjà en soi quelque chose d'illogique.

    Mais, les objets qui peuvent se déplacer ne devraient pas être en mesure de se déplacer "instantanément comme par magie" sur de longue distance: un objet se trouvant en position 1,1,1 ne devrait pas pouvoir se déplacer "comme cela", d'un "coup de baguette magique" à la position 100,1542,2340.

    Or, c'est ce qu'autorise le mutateur setPosition.

    On constate donc que le service que l'on attend des objets qui peuvent être déplacés tient au fait
    • soit de se déplacer d'une distance relative calculée en fonction d'un temps écoulé et de leur vitesse de déplacement
    • soit de se déplacer vers une position absolue, mais en un temps proportionnel à leur vitesse de déplacement et à la distance les séparant de cette position
    (Les deux possibilités pouvant cohabiter ) en tenant compte à chaque fois de leur éventuelle capacité de déplacement et / ou de leur orientation

    Tu ne devrais donc pas, à mon sens, avoir un mutateur setPosition, mais tu devrais avoir une fonction move(distance) (qui peut prendre la forme de move(temps) et/ou une fonction moveTo(position) qui prennent chaque fois en compte la position actuelle, la vitesse, l'orientation éventuelle et la capacité de mouvement de l'objet auquel on demande de se déplacer
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  18. #18
    Membre éclairé
    Avatar de Zenol
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2004
    Messages
    812
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2004
    Messages : 812
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Salut,
    (j'imagine que tu voulais parler du destructeur).
    Si le but est d'empêcher la manipulation de Localisable*, mettre le destructeur privé ne suffit pas. Dire 'je ne veux pas que Objet soit manipulé comme Localisable*', cela revient pour moi à dire que Localisable est un détail d'implémentation de Objet. Donc l'héritage devrait être privé.
    C'est d'ailleurs le seul moyen qui empêche un Objet d'être utilisé comme un Localisable. Le destructeur privé interdit juste la destruction d'un pointeur Localisable.
    Viendrais-tu du Java ? J'ai l'impression que tu assembles ta classe en indiquant quelles interfaces elle doit avoir (localisable, orientable, computable, etc.) ?
    J'ai quand même l'impression des éléments que tu présentes que ton héritage devrait être privé.
    Oui, destructeur, je m'excuse :$
    Tes arguments sont juste, mais si l'héritage est privé, qu'adviendra-t'il des méthodes publique fournie par Localisable?
    Puis après tout dans le fond, si l'on fait une liste d'objet localisable, on peu certes les mélanger et utiliser leurs méthodes... Mais ca n'a aucun sens.
    (Les "Objets" du raytracer sont Localisable, La caméra est localisable. Certaines des source lumineuses (Qui héritent toute de Light) sont localisable. Mélanger les trois dans une liste ne veux rien dire... Pourtant tous peuvent être positionner de la même façon, et ca évite une horrible redondance de code.)

    Je me suis peut-être mal exprimé en disant "Je ne veux pas qu'ils soient utilisé comme Loalisable*"... Je voulais plutôt dire "Je ne veux pas qu'on ai a manipuler de Localisable*, car ca n'a pas de sens".

    Maintenant, en y réfléchissant, il y à peut-etre moyen de faire quelque chose à base de template, pour que la classe Localisable n'existe pas, mais conserver l'unique implémentation des methodes?
    (Par exemple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <class T>
    class Localisable
    {
    // ...
    protected:
      ~Localisable();
    };
    class Object : public Localisable<Object>
    {
    //...
    De cette façon, au pire on manipuleras des Localisable<Object> ou des Localisable<Eye> (ou encore Localisable<PonctualLight>).
    Qu'en dis-tu?

    Et non je ne viens pas du java, j'ai fait 2-3ans de c++ "for fun" sans aborder des concepts complexe (Politique, classe de trait, patterns etc... Jamais toucher à ça, juste regardé de loin :/), puis 1 an de C intensif, et je me remet doucement au c++ par une ré-implémentation d'un raytracer (déjà fait en C sans model objet... pas très agréable à coder...)
    A vrais dire, je trouve que le principale défaut de java est de trop mettre des œillères aux programmeurs.

    Koala01 : Tu y tien à ta méthode move !?!
    Que deux objet s'intersectionnent n'est pas un problème, que deux objets soit l'un dans l'autre n'est pas non plus un problème, et que l'on décide que finalement une sphère se retrouve a l'autre bout de l'univers n'est pas non plus un problème. Quand à l'idée de mouvement (=> Faire un rendu sous forme d'animation), comme je l'ai dis plus tôt, on considèreras la scène comme figée à chaque "capture" par la caméra, et donc la position de chaque objet est fixe.
    Une scène décrit un paysage à un instant donné, il n'y a aucune raison pour qu'un objet puisse se déplacer.

    Quand à définir la position, l'orientation, la couleur, etc à la construction, c'est bien trop lourd.
    Je ne me vois pas faire ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Object *ellipsoid = new Ellipsoid(Position(2, 4, 8), Orientation(7, 3, 2), Color(233, 0, 59), transparence, indice de milieu, reflexion, indice de réflexion, indice de rugosité, rayon x, rayon y, rayon z);
    Ni non plus construire des constructeurs à rallonge pour chaque classe fille.
    Je préfère ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Object * ellipsoid = new Ellipsoid(rx, ry, rz);
    ellipsoid->setPosition(2, 4, 8);
    ellispoid->setDirection(7, 3, 2);
    ellipsoid->setColor(233, 0, 59);
    ellipsoid->setTransp(transparence);
    ellipsoid->setMediumIndex(indice de milieu);
    ellipsoid->setReflexion(réflexion);
    ellipsoid->setReflexionIndex(indice de réflexion);
    ellispoid->setRugosity(rugosité)
    Et de définir les caractéristique par défaut d'un objet. (Positionné en 0,0,0, pas de réflexion, ni de transparence, et parfaitement lisse. Enfin, blanc et orienté (1, 0, 0).)

    Sachant qu'il est tout à fait possible que je décide que demain, tout mes objets dispose d'une nouvelle caractéristique radiositée. Qu'adviendras t'il si un utilisateur (bon d'accord yen a pas, mais imaginons ) qui a implémenté ses propres objets, décide de récupère la nouvelle version du moteur et des classes de base? Il devras modifier toute ses classes qui décrivent de nouveaux objets. Alors que si je définie une valeur par défaut, nulle, ceci n'aurais aucun impacte et sa scène resteras inchangé.
    Mes articles Développez | Dernier article : Raytracer en haskell
    Network library : SedNL | Zenol's Blog : http://zenol.fr

    N'oubliez pas de consulter la FAQ et les cours et tutoriels.

  19. #19
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par Zenol Voir le message
    Koala01 : Tu y tien à ta méthode move !?!
    Oui, j'y tiens absolument
    Que deux objet s'intersectionnent n'est pas un problème, que deux objets soit l'un dans l'autre n'est pas non plus un problème, et que l'on décide que finalement une sphère se retrouve a l'autre bout de l'univers n'est pas non plus un problème. Quand à l'idée de mouvement (=> Faire un rendu sous forme d'animation), comme je l'ai dis plus tôt, on considèreras la scène comme figée à chaque "capture" par la caméra, et donc la position de chaque objet est fixe.

    Une scène décrit un paysage à un instant donné, il n'y a aucune raison pour qu'un objet puisse se déplacer.
    Ce que tu ne semble pas assimiler, c'est que chaque image représente l'état de ta scène à un instant T par rapport à une ligne de temps.

    Entre l'image à T-1 et l'image à T, tu appelles certains comportements particuliers, qui doivent veiller à garder la cohérence de ton image.

    Il n'y a pas de raison qu'un objet "proche" de toi à T-1 se retrouve de l'autre coté de l'univers à T.

    Par contre, entre T-1 et T, l'objet peut avoir évolué, en tournant sur lui-même, en se déplaçant dans une des trois dimensions, en s'opacifiant, en virant au rouge ou au noir, mais toujours dans une limite cohérent avec le temps qui s'est effectivement écoulé entre T-1 et T:

    Si un objet est orienté plein nord à T et que tu lui demande de s'orienter plein sud (en tourant vers la gauche), il sera, vraisemblablement orienté nord ouest à T+1, ouest à T+2, sud ouest à T+3, et sud à T+4 seulement (et encore, ici, il aura sans doute fait un demi tour très rapide)

    De même, si ton objet doit passer de la couleur bleue à la couleur rouge, il va, selon toutes vraisemblance, passer par différentes teintes présentant des rapports de bleu et de rouges différents: trois quarts bleu, un quart rouge à T+1; moitié moitié à T+2; trois quarts rouge un quart bleu à T+3 et rouge à T+4

    Si un objet doit "s'éloigner" du premier plan il peut s'éloigner de deux points en deux points si le rendu doit donner une sensation de lenteur ou de vingt en vingt si c'est un objet à "grande vélocité", mais, quoi qu'il en soit, chaque image ne représentera jamais que l'état de l'objet à un instant donné.
    Quand à définir la position, l'orientation, la couleur, etc à la construction, c'est bien trop lourd.
    Je ne me vois pas faire ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Object *ellipsoid = new Ellipsoid(Position(2, 4, 8), Orientation(7, 3, 2), Color(233, 0, 59), transparence, indice de milieu, reflexion, indice de réflexion, indice de rugosité, rayon x, rayon y, rayon z);
    C'est bien pour cela que je t'ai conseillé quelques messages plus haut de créer des structures regroupant ce qui "va ensemble":

    La position, l'orientation sont importantes pour donner pour positionner correctement un objet dans l'espace 3D, et vont particulièrement bien ensembles... regroupe les dans une structure dédiée

    Les trois rayons (x, y et z) sont important pour donner le volume à ton ellipsoïde et vont, eux aussi, particulièrement bien ensemble... fais de même

    La couleur, la transparence, l'indice de milieu, la réflexion, l'indice de réflexion et l'indice de rugosité sont des informations importantes pour le rendu de l'objet... regroupes les aussi dans une structure particulière.

    Au final, tu pourrais très bien avoir (je ne fais pas particulièrement attention aux types, il faudra les adapter )
    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
    struct PositionAndOrientation
    {
        Position pos;
        Orientation orient;
    };
    struct EllipsoidVolumeData
    {
        int distanceX;
        int distanceY;
        int distanceZ;
    };
    struct RenderData
    {
        Color color;
        int reflexion;
        int Indice;
        int transparence;
        int rugosity;
    };
    Et ton constructeur d'ellipsoide prend une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Ellipsoid
    {
        public:
            Ellipsoid(PositionAndOrientation const & orandpos, 
                      EllipsoidVolumeData const & evd,
                      RenderData const & rd): Object(orandpos.position, 
                      orandpos.orientation), Renderable(/*...*/),
                      xAxisRay(evd.distanceX), 
                      yAxisRay(evd.distanceY),zAxisRay(evd.distanceZ)
                      {
                      }
    };
    Et tu auras, enfin, ta fabrique qui, déjà, serait la seule à manipuler (en dehors des constructeurs de tes objets concrèts),qui pourrait présenter un fonctionnement 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
    Ellipsoid* createEllips(ifstream & ifs)
    {
        PositionAndOrientation posandor;
        EllipsoidVolumeData vd;
        RenderData rd;
        if(!(ifs>> posandor.pos.x>>posandor.pos.y>>posandor.pos.z
                >>posandor.orientation.angle
                >>vd.distanceX>>vd.distanceY>>vd.distanceZ
                >>rd.color.red>>rd.color.green>>rd.color.blue
                >>rd.reflexion>>rd.indice>>rd.transparency>>rd.rugosity))
        throw badFileFormat();
        return new Ellipsoid(posandor,vd,rd);
    }
    Enfin, une fois l'objet créé, tu ne va plus modifier directement la position, la couleur, le truc ou le chose, mais tu va appeler des comportements qui réflèteront les différentes transitions entre deux "instantanés" servant au rendu de l'image:
    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
    void foo()
    {
        std::ifstream myFile("lenom.txt");
        Scene scene;
        Object * ptr = createEllips(myFile);
        /* ajout de ptr à la scene */
        /*stop: on crée un instantané de la scene */
        scene.render();
        /* on applique certaines tansition */
        ptr->rotate(45*PI/180);
        ptr->move(+3);
        ptr->moreTransparent(2);
        ptr->moreRed§(5);
        ptr->lessBlue(5);
        /* stop, nouveau rendu */
        scene.render();
    }
    Ni non plus construire des constructeurs à rallonge pour chaque classe fille.
    Je préfère ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Object * ellipsoid = new Ellipsoid(rx, ry, rz);
    ellipsoid->setPosition(2, 4, 8);
    ellispoid->setDirection(7, 3, 2);
    ellipsoid->setColor(233, 0, 59);
    ellipsoid->setTransp(transparence);
    ellipsoid->setMediumIndex(indice de milieu);
    ellipsoid->setReflexion(réflexion);
    ellipsoid->setReflexionIndex(indice de réflexion);
    ellispoid->setRugosity(rugosité)
    Et de définir les caractéristique par défaut d'un objet. (Positionné en 0,0,0, pas de réflexion, ni de transparence, et parfaitement lisse. Enfin, blanc et orienté (1, 0, 0).)
    Il n'est pas logique de le faire parce que, une fois l'objet créé, tu ne devrais plus rien faire d'autre que d'y appliquer des transitions... dont le résultat sera visible... au prochain instantané réalisé lors du rendu de l'image...
    Sachant qu'il est tout à fait possible que je décide que demain, tout mes objets dispose d'une nouvelle caractéristique radiositée. Qu'adviendras t'il si un utilisateur (bon d'accord yen a pas, mais imaginons ) qui a implémenté ses propres objets, décide de récupère la nouvelle version du moteur et des classes de base? Il devras modifier toute ses classes qui décrivent de nouveaux objets. Alors que si je définie une valeur par défaut, nulle, ceci n'aurais aucun impacte et sa scène resteras inchangé.
    Justement, si tu expose le moins possible les détails d'implémentation de ta classe, et que tu n'expose qu'une interface "stable", tu peux, à tout moment, décider de rajouter un comportement donné, agissant de manière donnée sur les détails d'implémentation, voir, modifier complètement la manière dont les détails d'implémentations sont représentés sans que cela n'occasionne de changements ailleurs que... dans l'implémentation des comportement propres à ta classe.

    Le code qui utilise ta classe ne devra absolument pas être modifié pour autant que tu ne modifie pas l'interface proposée.

    Tu auras beaucoup plus de mal à y arriver si tu expose des mutateurs tels que setPosition ou setColor
    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

  20. #20
    Membre éclairé
    Avatar de Zenol
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2004
    Messages
    812
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2004
    Messages : 812
    Par défaut
    Je ne suis pas du tout d'accord avec ta vision. Pour moi il n'y a aucune raison de restreindre l'utilisation en obligeant ce dernier à devoir, si il décidé de placer la sphère en 10,10,1, calculer quel est le mouvement correspondant.

    L'animation seras gérer par une partie complètement indépendante du moteur de rendu.

    De plus, ce n'est pas parce que notre univers fonctionne d'une certaine façon, et que les théories adopté par les physiciens suppose qu'une grande partie des fonctions sont continue que l'on ne peux pas imaginer de nouvelles règles.

    Par exemple, dans un univers fermer, un objet qui était en 0,0,99 et passe en 0,0,100 peux en réalité se retrouver en 0,0,0 !

    Le bute du moteur est clairement de recevoir une structure qui décris une scène, et rendre l'image associée. Ni plus, ni moins. Je n'ai pas à définir de politique d'utilisation, seulement fournir une interface qui permet de faire un rendu. (Tout comme un driver ne dois pas définir de politique, mais seulement des fonctions qui permettent de contrôler le matériel.)

    Définir des mouvements, c'est déjà tenir compte de l'existence d'une variable "temps", et de faire l'hypothèse d'une certaine continuités. Ce que je me refuse.

    Ce que tu ne semble pas assimiler, c'est que chaque image représente l'état de ta scène à un instant T par rapport à une ligne de temps.
    Et si il n'y a pas de variable temps? Pourquoi devrais-til nécessairement évoluer? Si l'on considère notre univers de dimension 4[en simplifiant] (x, y, z, t) on peut considère un univers de dimension 3[x, y, t] et le "dessiner" dans un repère x,y,z (Via des surfaces)
    Alors, plus rien ne "bouge", puisque regarder l'espace a T+1 c'est regarder les courbes former par l'intersection du plan Z=T+1 et de la/les surface(s)...

    Bref, vouloir imposer l'existence d'un temps n'a pas lieux d'être.

    ---

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Et tu auras, enfin, ta fabrique qui, déjà, serait la seule à manipuler (en dehors des constructeurs de tes objets concrèts),qui pourrait présenter un fonctionnement proche de...
    La aussi, tu fait un hypothèse. L'hypothèse que les seuls objets qui existent sont mes objets... Hors, rien n'empêche l'utilisateur de créer son propre "Objet", héritant d'Objet, et redéfinissant les fonctions de calcule de normale, etc, etc.
    Alors, dans le cas de l'ajout d'une nouvelle caractéristique à tout objet, on se retrouve à devoir :
    -Ou bien rajouter la valeur uniquement dans une des structures de groupe existante, ce qui suppose que l'on connais a l'avance toute les possibilité ou bien qu'on a une structure "four tout" en rab.
    -Ou bien définir cette valeur par défaut.

    De plus, rajouter une nouvelle valeur dans une structure pose problème quand a son initialisation, si elle n'existait pas dans le code précédant.

    L'idée de la factory, c'est cool seulement si on se limite à mes objets. Si on a de nouveaux objets, réaliser par l'utilisateur, ca peux poser problème. (L'utilisateur n'a pas à toucher au code relatif au rendu. La factory en faisant partie...)

    Enfaite, la possibilité de ne définir que ce dont un a besoin, et non tout à chaque création d'objet devrais être vue comme une qualité plus qu'un défaut.
    Cela permet d'avoir des fichiers texte décrivant une scène plus claire, plus human-readable, que de revoir sur 50ligne que tous les objets on 0 de transparence...
    On peut même envisager une fonction Oject::copyCaracts(Object*) qui reproduit la couleur, rugosités, etc... d'un objet passé en paramètre. La encore, définir ca au constructeur serais plus une contrainte qu'autre chose, et ne faciliterais pas l'utilisation.

    Dans le cas de valeur près définie, cela ne pause plus non plus d'incompatibilité entre les fichiers de scène. On ne fait que rajouter des options possible, et non imposer une nouvelle syntaxe. Donc, un vieux fichier seras tout comme un nouveaux, qui ommet simplement de décrire les nouvelles caractéristiques.

    En s'imposant que l'ajout d'une nouvelle caractéristique ne doit pas changer le rendu si l'on ne modifie pas la valeur, on s'assure d'une rétro-compatibilités pour :
    -Les fichiers de scène
    -Les utilisations en C++ du code
    -Les utilisations de binding (ex: python)

    De plus, avec cette structure, il est simple de construire une factory qui permet de créer aussi les objets de l'utilisateur!
    La sérialisation est alors très simple sous réserve que chaque Objet puisse fournir son Nom (virtual const char *getName() const ) et puisse sérialiser ses données particulières.
    La reconstruction est elle plus complexe, et nécessite que l'utilisateur définisse un foncteur/pointeur de fonction/fonction membre qui décris l'étape de construction à partir d'un vecteur d'arguments.
    Effectivement, cela peux aussi se faire avec ta méthode, mais a chaque nouvelle ajout de caractéristique, on doit modifier tout le code de toutes les fonctions create... Et si on laisse a l'utilisateur la possibilité de définir sa fonction create(hors de la factory), il devras lui aussi la modifier après une maj.

    Avant d'appliquer un concepts, je crois qu'il faut toujours penser aux répercussions à l'utilisation. Et tes idées, selon moi, ne vont pas dans le sens d'une interface flexible (Pour ce cas particulier seulement).
    Ma vision est probablement plutôt simpliste, mais elle a le mérite de ne pas limiter l'utilisateur et de ne pas faire de supposition sur la politique.
    Mes articles Développez | Dernier article : Raytracer en haskell
    Network library : SedNL | Zenol's Blog : http://zenol.fr

    N'oubliez pas de consulter la FAQ et les cours et tutoriels.

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Réponses: 2
    Dernier message: 09/09/2007, 14h25
  2. Question bateau sur la conversion si possible d'objets
    Par kenny49 dans le forum Langage
    Réponses: 1
    Dernier message: 08/08/2007, 19h20
  3. Réponses: 8
    Dernier message: 04/06/2007, 16h20
  4. Réponses: 24
    Dernier message: 01/06/2007, 09h26
  5. Réponses: 8
    Dernier message: 11/07/2006, 17h27

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