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

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

C++ Discussion :

Avis sur un problème de "friend class template"


Sujet :

C++

  1. #1
    Membre éclairé Avatar de metagoto
    Profil pro
    Hobbyist programmateur
    Inscrit en
    Juin 2009
    Messages
    646
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Hobbyist programmateur

    Informations forums :
    Inscription : Juin 2009
    Messages : 646
    Points : 845
    Points
    845
    Par défaut Avis sur un problème de "friend class template"
    Lut.

    Dans c++03, la déclaration d'une class friend ne peut pas être un type dépendant. En gros, on ne peut pas faire ceci:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template<typename T>
    struct A
    {
      // illegal. "T" est un template argument
      friend class T;
    };
    Maintenant si T est lui même paramétré par rapport à A, ça ne va pas arranger les choses.

    Pour comprendre ce que je souhaite faire, voici un code réduit au minimum, qui fonctionne et qui fait intervenir les bonnes vieilles notions de Car et Engine

    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
    #include<iostream>
    template<typename Engine>
    struct car 
    {
      void start() {
        Engine e;
        e.priv = 1;
        std::cout << e.priv;
      }  
    };
     
    struct engine
    {
      // members...
    private:
      // seul un "car" est sensé instancier un engine
      engine() : priv(0) { }
     
      int priv;
     
      // OK. mais prob: le type "car" est trop spécifique
      template<typename Engine> friend class car;
    };
     
    int main()
    {
      car<engine> c;
      c.start();
      return 0;
    }
    Ce que je souhaiterais faire pour la class engine est 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
    // pas bon. ridicule.
    // pas bon non plus avec template<template<typename T> class Car>
    template<typename Car>
    struct engine
    {
      // members...
    private:
      engine() : priv(0) { }
     
      int priv;
     
      // illegal. "Car" est un template argument
      template<typename Engine> friend class Car;
    };
    On comprend tout de suite que les types car et engine sont mutuellement corrélés et on tombe sur un problème de déclaration récursive (en plus du problème de déclaration du friend).

    Pour contourner le problème, j'ai fait ceci:

    - la classe car dérive d'une class template car_base qui sera friend de engine:
    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
    template<typename Engine>
    struct car_base
    {
      // membres pour accéder à certains membres privates de Engine 
    };
     
    template<typename Engine>
    struct car : public car_base<Engine>
    {
      typedef car_base<Engine> base_type;
      void start() {
        // utilise les membres de base_type pour accéder
        // à l'interface privée de Engine
      }
    };
    - le type paramétré car_base est déclaré friend de engine:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    struct engine
    {
      // members...
      // ...
     
      // OK. "car_base" est friend
      template<typename Engine> friend class car_base;
    };
    Je peux maintenant composer avec des types raffinés tel que advanced_car ou advanced_engine:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template<typename Engine>
    struct advanced_car : public car_base<Engine>
    { 
      // ..
    }
     
    advanced_car<engine> ac;
     
    car<advanced_engine> aac;
     
    advanced_car<advanced_engine> aaac;
    En conclusion, Car peut varier indépendamment de Engine. La contrainte est que Engine reste compatible avec la façade que constitue car_base.

    Tout ceci est destiné à être utilisé dans un context plus vaste basé sur les policies. Je me fiche que advanced_car ne dérive pas de car dans l'exemple.

    Est-ce que ce "trick" de la base classe paramétrée qui sera friend de son paramètre vous parait pertinent. Une autre solution ?

  2. #2
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Pourquoi ne remplaces-tu pas simplement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
      // OK. mais prob: le type "car" est trop spécifique
      template<typename Engine> friend class car;
    par :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
      // OK. mais prob: le type "car" est trop spécifique
      friend class car<engine>;
    Si j'ai bien compris, seule car est paramétrée, pas engine, donc tu n'es pas dans le premier cas que tu décris (où tu dois déclarer friend le paramètre template).

  3. #3
    Membre éclairé Avatar de metagoto
    Profil pro
    Hobbyist programmateur
    Inscrit en
    Juin 2009
    Messages
    646
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Hobbyist programmateur

    Informations forums :
    Inscription : Juin 2009
    Messages : 646
    Points : 845
    Points
    845
    Par défaut
    @white_tentacle

    Je ne suis pas dans le "premier cas" (où je dois déclarer friend le paramètre template) car je ne m'en sort pas sinon. Idéalement c'est ce que j'aimerai mais on tombe dans une situation où les 2 types sont paramétrés l'un par rapport à l'autre (sans que l'on soit dans un CRTP) et il y a bien sur le fait que "friend" ne fonctionne pas avec un type paramètre.

    Sinon pour la suggestion:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    friend class car<engine>;
    En fait ça serait la solution la plus simple, effectivement. Sauf que je souhaite que la classe engine puisse fonctionner avec n'importe quel car. Je ne peux/veux pas "hard coder" la classe car en tant que friend.

    Du coup, je passe par cette base class car_base. J'accèpte de "hard coder" car_base dans les classes Engine. Du coup l'interface de car_base manipulée par les classes qui en héritent (les car) constitue le "point d'invariance" (ou de compatibilité) entre un Engine et un Car. Tout le reste est laissé libre et c'est ce que je souhaite car il s'agit de "composer" dans un cadre de policies. Je ne sais pas si je me fais bien comprendre

  4. #4
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Je crois que j'ai compris. Effectivement, je ferais comme ça (il me semble que c'est ce que tu décris) :

    dans engine, on déclare comme classe amie car_base.

    Dans car_base, j'implémente des wrapper qui permettent d'accéder aux propriétés de engine que je veux modifier (et uniquement ça).

    Puis, pour chacun de mes car, je fais un héritage privé ou protégé (mais pas public, pour ne pas être polymorphique) de car_base. Dans car, j'utilise les wrapper de car_base pour modifier les propriétés de engine.

    Le schéma peut être symétrique si engine doit modifier des propriétés de car, mais ça voudrait dire que tu as un couplage très (trop) fort entre tes deux classes.

  5. #5
    Membre éclairé Avatar de metagoto
    Profil pro
    Hobbyist programmateur
    Inscrit en
    Juin 2009
    Messages
    646
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Hobbyist programmateur

    Informations forums :
    Inscription : Juin 2009
    Messages : 646
    Points : 845
    Points
    845
    Par défaut
    Merci white_tentacle, tu as parfaitement compris.

    Par chance (ou par un design judicieux ), le schéma n'a pas à être symétrique (Engine n'a pas besoins de manipuler Car).

    Je ne sais pas si ce "design pattern" porte un nom.

  6. #6
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par metagoto Voir le message
    Je ne sais pas si ce "design pattern" porte un nom.
    Une erreur ?

    En gros, tu est en train de nous expliquer que
    * soit seules les car ont le droit de manipuler l'interface des engine
    * soit tu autorises les car à manipuler l'intérieur des engine.

    Ni l'une ni l'autre de ces possibilité n'a de chance d'être valide en termes d'architecture logicielle. Si engine existe et défini une interface publique, alors ça veut dire que tout le monde peut utiliser un engine : une instance de car, mais aussi un mécanicien ou un banc de test. Si engine ne définit pas d'interface publique, alors engine n'a pas à exister en tant que classe (une classe fournit des services; si elle ne fournit pas de service, c'est qu'elle ne fait rien).

    Maintenant, si ton design repose sur le fait que jamais rien d'autre qu'une instance de car ne manipulera un engine, alors tu peux peut-être faire de engine une inner-class de car.

    Autre solution : si car n'a besoin que d'une partie des services offerts par engine (et que, d'un autre coté, d'autres classes peuvent utiliser d'autres services de engine), alors il manque peut être un niveau d'indirection à ton design - des adapteurs probablement (car_engine_adapter, ...). Ce qui ne veut pas dire que engine ne doit pas exposer l'intégralité des services qu'elle offre à ses différents clients.
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  7. #7
    Membre éclairé Avatar de metagoto
    Profil pro
    Hobbyist programmateur
    Inscrit en
    Juin 2009
    Messages
    646
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Hobbyist programmateur

    Informations forums :
    Inscription : Juin 2009
    Messages : 646
    Points : 845
    Points
    845
    Par défaut
    Citation Envoyé par Emmanuel Deloget Voir le message
    Une erreur ?

    En gros, tu est en train de nous expliquer que
    * soit seules les car ont le droit de manipuler l'interface des engine
    * soit tu autorises les car à manipuler l'intérieur des engine.

    [...]

    Maintenant, si ton design repose sur le fait que jamais rien d'autre qu'une instance de car ne manipulera un engine, alors tu peux peut-être faire de engine une inner-class de car.
    Une erreur ?

    Les engines ont aussi une interface publique. Ils sont amenés à être manipulés par d'autre composants. Mais leur création, par exemple, ne peut être faite que par un car. (encore une fois, mon projet réel ne manipule pas des Car et des Engine. C'est juste pour l'exemple)

    Citation Envoyé par Emmanuel Deloget Voir le message
    Autre solution : si car n'a besoin que d'une partie des services offerts par engine (et que, d'un autre coté, d'autres classes peuvent utiliser d'autres services de engine), alors il manque peut être un niveau d'indirection à ton design - des adapteurs probablement (car_engine_adapter, ...). Ce qui ne veut pas dire que engine ne doit pas exposer l'intégralité des services qu'elle offre à ses différents clients.
    Pour l'intant, je ne pense pas devoir recourir à des d'adapters. Mais effectivement, c'est une possibilité.

  8. #8
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par Emmanuel Deloget Voir le message
    Une erreur ?

    En gros, tu est en train de nous expliquer que
    * soit seules les car ont le droit de manipuler l'interface des engine
    * soit tu autorises les car à manipuler l'intérieur des engine.
    C'est d'ailleurs parce que c'est une erreur de conception que Eiffel permet de définir explicitement quelles classes ont accès à quelles fonctionnalités d'une autre classe

    Il faut arrêter de présenter le modèle private/protected/public comme vérité ou bonne manière de concevoir : c'est au contraire une limitation qui nous est malheureusement imposée par le langage.

    Pour prendre une analogie, je prête ma voiture à mes amis, mais pas tous, et en revanche, je donne l'heure à n'importe qui me la demande. Cette granularité a tout son sens, et on peut la retrouver aussi en conception informatique. Dire que c'est une erreur que de vouloir reproduire cette granularité au moyen des maigres outils fournis par le langage (en l'occurrence, friend), me semble... une erreur .

  9. #9
    Membre éclairé Avatar de metagoto
    Profil pro
    Hobbyist programmateur
    Inscrit en
    Juin 2009
    Messages
    646
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Hobbyist programmateur

    Informations forums :
    Inscription : Juin 2009
    Messages : 646
    Points : 845
    Points
    845
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Dire que c'est une erreur que de vouloir reproduire cette granularité au moyen des maigres outils fournis par le langage (en l'occurrence, friend), me semble... une erreur .
    C'est quand même pas si maigre que ça. C'est vrai, heureusement que c++ supporte la notion de frienship (sinon ça serait rasoir ).

    Toujours rien pour le nom de ce pattern/trick de "la base classe paramètrée qui est friend de son paramètre" permettant une composition orthogonale de Car et Engine ? (je devrais parler de Counter et Shape à la place ? )

Discussions similaires

  1. Friend Classe Template
    Par coberle dans le forum C++
    Réponses: 5
    Dernier message: 29/03/2013, 13h19
  2. Besoin de votre avis sur un problème
    Par petitcoucou31 dans le forum Langage
    Réponses: 7
    Dernier message: 30/06/2008, 16h33
  3. Réponses: 24
    Dernier message: 11/06/2008, 15h26
  4. Problème avec vector de classe template :(
    Par coyotte507 dans le forum Langage
    Réponses: 7
    Dernier message: 16/04/2008, 12h40
  5. Avis sur gros problème BDE en Reseau
    Par tipiweb dans le forum Bases de données
    Réponses: 4
    Dernier message: 29/04/2005, 09h25

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