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 :

Comment créer une classe template qui s'adapte à différents types de pointeurs (intelligents ou pas) ?


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre du Club Avatar de lilivve
    Homme Profil pro
    Creative coding
    Inscrit en
    Mars 2018
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Creative coding
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mars 2018
    Messages : 7
    Par défaut Comment créer une classe template qui s'adapte à différents types de pointeurs (intelligents ou pas) ?
    Bonjour,

    J'ai des pointeurs vers des instances de différentes classes A, B, C, D.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
                     A * a;
    std::unique_ptr< B > b;
    std::shared_ptr< C > c;
    std::weak_ptr  < D > d;
    Toutes ces classes ont une methode getId() renvoyant une std::string.
    Je souhaite créer une classe template Controller< T > de ce type:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template< T > class Controller
    {
        T target;
    public:
        Controller( const T & target ) : target{ target } {}
        void print(){ cout << target->getId(); }
    };
    Et je voudrais pouvoir l'utiliser avec tous les types de pointeurs cités plus haut:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Controller< A > aCtrl( a ); aCtrl.print();
    Controller< B > bCtrl( b ); bCtrl.print();
    Controller< C > cCtrl( c ); cCtrl.print();
    Controller< D > dCtrl( d ); dCtrl.print();
    Dans le cas d'un share_ptr j'aimerais pouvoir choisir si le Controller partage la responsabilité de l'objet pointé en le référençant sous forme de shared_ptr, ou pas en le référençant comme weak_ptr.

    Sachant que j'ai encore peu d'expérience avec les templates, j'ai écris une solution avant de venir poser cette question. J'imagine que je suis loin d'être le premier à me confronter à ce problème, mais il est difficile de trouver une réponse dans l'abondance de sujets traitant des templates et des pointeurs.

    Mes questions sont:
    - est-ce que je m'y prends bien ?
    - y a-t-il plus simple ?
    - suis-je en train de réinventer la roue pour rien ?

    Voici ce que j'ai commis. L'idée est d'ajouter un second paramètre template qui permet l'adaptation aux différents types de pointeurs (il est possible que ce soit ce qu'on appelle une classe de trait, je ne suis pas sûr, je découvre encore le sujet).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <
    	class Target,
    	template < class > class Adapter
    >
    class Controller
    Une classe Adapteur est de la forme:
    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
    template < class T >
    class AnAdapter
    {
    	public:
     
    		// Le type de paramètre à passer au constructeur de Controller
    		using Source = ??? ; // au choix T *, unique_ptr<T>, shared_ptr< T >, weak_ptr<T>, et éventuellement autre chose
     
    		// Le type de Controller::target
    		using Storage = ??? ; // // au choix T *, shared_ptr< T > ou weak_ptr<T>
     
    		// Fabrique un objet Storage à partir d'un paramètre Source
    		static Storage makeStorage( const Source & t );
     
    		// Renvoie un pointeur qui permet d'accéder à l'objet controllé
    		// avec la même syntaxe, ici cout << ptr( target )->getId();
    		static T * ptr( Storage & t );
    } ;
    La classe controller est donc:
    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
    template <
    	class Target,
    	template < class > class Adapter
    >
    class Controller
    {
    	private:
     
    		using Adpt = Adapter< Target > ;
    		using Source = typename Adpt::Source ;
    		using Storage = typename Adpt::Storage ;
     
    		Storage target ;
     
    	public:
     
    		Controller( const Source & target ) : target{ Adpt::makeStorage( target ) }
    		{ }
     
    		void setTarget( const Source & target )
    		{
    			this->target = Adpt::makeStorage( target ) ;
    		}
     
    		void printTargetId()
    		{
    			auto ptr = Adpt::ptr( target ) ;
    			if( ptr ) cout << "Target is " << ptr->getId() << endl ;
    			else cout << "No target" << endl ;
    		}
    } ;
    Merci pour votre lecture, je vous laisse avec un exemple fonctionnel complet comprenant plusieurs adaptateurs (et une seule classe A, pas la peine d'alourdir avec B, C et D je pense):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    #include <iostream>
    #include <memory>
    #include <string>
     
    using namespace std ;
     
    // Store and use a raw pointer
    template < class T >
    class RawPointerAdapter
    {
    	public:
    		using Source = T * ;
    		using Storage = T * ;
    		static Storage makeStorage( const Source & t ){ return t ; }
    		static T * ptr( Storage & t ){ return t ; }
    } ;
     
    // Store and use a raw pointer obtained from an unique_ptr
    template < class T >
    class RawPointerFromUniqueAdapter
    {
    	public:
    		using Source = unique_ptr< T > ;
    		using Storage = T * ;
    		static Storage makeStorage( const Source & t ){ return t.get() ; }
    		static T * ptr( Storage & t ){ return t ; }
    } ;
     
    // Store and use a shared_ptr
    template < class T >
    class SharedPointerAdapter
    {
    	public:
    		using Source = shared_ptr< T > ;
    		using Storage = shared_ptr< T > ;
    		static Storage makeStorage( const Source & t ){ return t ; }
    		static shared_ptr< T > & ptr( Storage & t ){ return t ; }
    } ;
     
    // Store and use a weak_ptr, obtained by either a weak_ptr or a shared_ptr
    template < class T >
    class WeakPointerAdapter
    {
    	public:
    		using Source = weak_ptr< T > ;
    		using Storage = weak_ptr< T > ;
    		static Storage makeStorage( const Source & t ){ return t ; }
    		static shared_ptr< T > ptr( Storage & t ){ return t.lock() ; }
    } ;
     
    template <
    	class Target,
    	template < class > class Adapter
    >
    class Controller
    {
    	private:
     
    		using Adpt = Adapter< Target > ;
    		using Source = typename Adpt::Source ;
    		using Storage = typename Adpt::Storage ;
     
    		Storage target ;
     
    	public:
     
    		Controller( const Source & target ) : target{ Adpt::makeStorage( target ) }
    		{ }
     
    		void setTarget( const Source & target )
    		{
    			this->target = Adpt::makeStorage( target ) ;
    		}
     
    		void printTargetId()
    		{
    			auto ptr = Adpt::ptr( target ) ;
    			if( ptr ) cout << "Target is " << ptr->getId() << endl ;
    			else cout << "No target" << endl ;
    		}
    } ;
     
    class A
    {
    	string id ;
    	public:
    		A( string id ) : id{ id }{ }
    		const string & getId(){ return id ; }
    } ;
     
    int main()
    {
    	cout << endl << "RawPointerAdapter ------------------------------" << endl ;
    	A a( "Target 1-1" ) ;
    	A * aPtr = new A( "Target 1-2" ) ;
    	Controller< A, RawPointerAdapter > ctrlRaw( &a ) ;
    	ctrlRaw.printTargetId() ;
    	ctrlRaw.setTarget( aPtr ) ;
    	ctrlRaw.printTargetId() ;
     
    	cout << endl << "RawPointerFromUniqueAdapter --------------------" << endl ;
    	auto unik1 = make_unique< A >( "Target 2-1" ) ;
    	auto unik2 = make_unique< A >( "Target 2-2" ) ;
    	Controller< A, RawPointerFromUniqueAdapter > ctrlUnik( unik1 ) ;
    	ctrlUnik.printTargetId() ;
    	ctrlUnik.setTarget( unik2 ) ;
    	ctrlUnik.printTargetId() ;
    	unik2.reset() ;
    	// Don't do this because the stored pointer is invalid !
    	// ctrl2.printTargetId() ;
     
    	cout << endl << "SharedPointerAdapter ---------------------------" << endl ;
    	auto sh1 = make_shared< A >( "Target 3-1" ) ;
    	auto sh2 = make_shared< A >( "Target 3-2" ) ;
    	auto sh3 = sh2 ;
    	Controller< A, SharedPointerAdapter > ctrlShared( sh1 ) ;
    	ctrlShared.printTargetId() ;
    	ctrlShared.setTarget( sh2 ) ;
    	ctrlShared.printTargetId() ;
    	ctrlShared.setTarget( sh3 ) ;
    	ctrlShared.printTargetId() ;
    	sh3.reset() ;
    	ctrlShared.printTargetId() ;
    	sh2.reset() ;
    	ctrlShared.printTargetId() ; // Still work because ctrl3 store the shared_ptr
     
    	cout << endl << "WeakPointerAdapter -----------------------------" << endl ;
    	weak_ptr< A > wk1 = sh1 ;
    	Controller< A, WeakPointerAdapter > ctrlWeak( wk1 ) ;
    	ctrlWeak.printTargetId() ;
    	ctrlWeak.setTarget( sh1 ) ;
    	ctrlWeak.printTargetId() ;
    	sh1.reset() ;
    	ctrlWeak.printTargetId() ; // Print "No target"
     
    	return 0 ;
    }

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

    Les pointeurs intelligents (qu'il s'agisse de std::unique_ptr ou du couple std::shared_ptr + std::weak_ptr) ne poursuivent qu'un seul et unique but : permettre de déterminer le propriétaire légal du pointeur sous-jacent.

    Ou, si tu préfères, qui devra s'occuper de libérer la mémoire allouée au pointeur sous-jacent lorsqu'il sera devenu inutile de le maintenir en mémoire.

    Dans le couple std::shared_ptr + std::weak_ptr, le std::weak_ptr joue le rôle d'un "pointeur temporaire", pour lequel on prévoit qu'il doive être utilisé (et qu'il ne puisse, dés lors, plus être détruit avant la fin de l'utilisation que l'on a prévue à son égard), mais dont l'utilisation n'a pas encore débuté, de manière à ce que, s'il venait à être détruit avant que l'utilisation prévue ne commence, on puisse encore décider de ne pas l'utiliser.

    Tout cela pour dire que tu n'as pas besoin de faire la distinction entre un std::unique_ptr et un std::shared_ptr, parce que, ce qui importe, c'est le pointeur sous-jacent; au sujet duquel, après t'être assuré qu'il s'agit d'un pointeur valide, tu auras largement intérêt à envisager de le transmettre sous forme de référence (éventuellement constante) à toute fonction qui n'a pas vocation à intervenir dans la décision d'en libérer la mémoire.

    Quant à std::weak_ptr, tu n'as pas besoin de t'en inquiéter, car, une fois que tu décideras d'utiliser le pointeur sous-jacent, tu devras d'office créer un ... std::shared_ptr (il est impossible d'accéder au pointeur sous-jacent d'un std::weak_ptr sans en faire un std::shared_ptr )

    Plutôt que de t'inquiéter de savoir quel pointeur intelligent enrobe un pointeur pour lequel tu as eu recours (fusse de manière indirecte) à l'allocation dynamique de la mémoire, pourquoi ne pas travailler directement sur le type de la donnée à laquelle ce pointeur te donne accès

    Toutes ces classes ont une methode getId() renvoyant une std::string.
    Humm ... getId, ca, ca sent une forme personnalisée du RTTI à plein nez!!! C'est le meilleur moyen de te préparer les pires emmerdes, et les bugs les plus nombreux

    En plus, l'utilisation d'une chaîne de caractères à cet effet est vraiment de nature à plomber un maximum les performances, à cause de la logique nécessaire à la comparaison de ce type de données.

    Considère l'utilisation du double dispatch, lorsque tu as été "assez bête" pour perdre le type réel de la donnée pointée et que tu as besoin de connaître ce type réel... Cela t'évitera bien des soucis par la suite .

    Mieux encore, si tu peux faire en sorte de te limiter à la connaissance de l'abstraction représentée par le type de base, en faisant en sorte que seuls les services exposés par le type de base doivent être utilisés (tout en respectant LSP, cela va de soi), tu ne pourras que t'en sentir beaucoup mieux dans quelques mois
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre du Club Avatar de lilivve
    Homme Profil pro
    Creative coding
    Inscrit en
    Mars 2018
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Creative coding
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mars 2018
    Messages : 7
    Par défaut
    Salut koala,

    Ah, mince, maintenant je me demande si j'ai été assez clair dans ma question ! Car je souhaite avoir des avis sur l'implémentation de la fonctionnalité que je développe, mais tu questionnes la conception et la nécessité de développer une telle fonctionnalité. J'apprécie, ça m'intéresse, mais je crains de noyer ma question s'y on part là-dedans. Je vais créer une discussion spéciale pour te répondre, saurais-tu me conseiller un salon pour le faire (sinon j'utiliserai celui-ci) ?

    Le getId() n'est pas ce que tu imagines, c'était pour l'exemple, la prochaine fois je mettrais foo() !

    Merci pour ta réponse

  4. #4
    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
    De manière générale, la programmation générique se base sur le fait que
    si on ne sait pas exactement quel type de donnée on va manipuler, on sait en revanche parfaitement comment on devra les manipuler
    C'est la raison pour laquelle j'attire ton attention sur le fait que, que tu travailles avec un std::unique_ptr ou avec un couple std::shared_ptr + std::weak_ptr, tu ne dois pas t'inquiéter du pointeur intelligent qui prendra la responsabilité de la libération de la ressource, mais que tu dois t'inquiéter uniquement du type de la ressource qu'ils maintiennent en mémoire.

    Ainsi, si tu base ta conception sur le fait que "plusieurs types de données exposent une fonction bien précise" que tu envisage d'utiliser dans une fonction générique, tu peux, tout simplement, écrire ta fonction sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T>
    void doSomething(T /* const*/ & value){
        value.foo();
        /* ... */
    }
    Par la suite, lorsque tu voudras utiliser cette fonction sur des éléments polymorphes (car l'utilisation de pointeurs intelligents n'a de sens que pour les éléments polymorphes), tu pourras toujours avoir un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    std::unique_ptr<UnType> unique:
    /* ... */
    doSomething(*(unique.get());
    /* ... */
    std::shared_ptr<AutreType> shared:
    /* ... */
    doSomething(*(shared.get());[
    std::weak_ptr<AutreType> weak{shared};
    /* .. */
    doSomething(*(weak.lock().get());
    Comme tu peux t'en rendre compte, tu n'as absolument aucune raison de t'inquiéter du type du pointeur intelligent utilisé

    Notes que, au pire, tu peux toujours envisager le recours à la spécialisation partielle de ta fonction :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /* on crée une spécialisation spécifique pour l'utilisation de std::unique_ptr */
    template <typename T>
    void doSomething(std::unique_ptr<T> /* const */ & ptr){
        /* ... */
    }
    /* Même chose pour les shared_ptr */
    template <typename T>
    void doSomething(std::shared_ptr<T> /* const */ & ptr){
        /* ... */
    }
    /* rappel : spécialiser pour std::weak_ptr n'a pas de sens ;) */
    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

  5. #5
    Membre du Club Avatar de lilivve
    Homme Profil pro
    Creative coding
    Inscrit en
    Mars 2018
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Creative coding
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mars 2018
    Messages : 7
    Par défaut
    Merci pour cette nouvelle réponse et sa clarté

    Je vois bien que dans ce que j'ai proposé, prévoir l'utilisation d'un unique_ptr n'est pas du tout indispensable, puisque cela revient au même qu'utiliser un pointeur nu, à ceci près que cela permet de faire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    std::unique_ptr< B > b;
    Controller< B,RawPointerFromUniqueAdapter > bCtrl( b );
    bCtrl.print();
    plutôt que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    std::unique_ptr< B > b;
    Controller< B, RawPointerAdapter > bCtrl( b.get() );
    bCtrl.print();
    J'adhèrerai à tout ce que tu dis si la classe Controller ne référençait pas l'objet qu'elle contrôle.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template< T > class Controller
    {
        T target;
        // etc
    }
    C'est là la difficulté. Et comme elle le fait je ne vois pas comment appliquer ce que tu préconises sans imposer à l'utilisateur de la classe Controller des choix là où je voudrais rester générique. Là encore j'imagine qu'on peut s'interroger sur la pertinence du fait que Controller référence l'objet qu'elle contrôle. Alors comme je disais je préfère qu'on parle ailleurs de mon choix de design. J'ai ouvert un sujet spécial pour cela, comme cela on pourra s'en tenir ici à ma question qui porte sur l'implémentation technique de ce que je veux faire, pas sur son bien-fondé !

  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
    Je crois que, de toutes manières, tu as un sérieux problème de conception, ne serait-ce que parce que le terme "contrôleur" est en soi beaucoup trop vague pour ton propre bien (c'est comme le terme "manager" sur ce coup là ).

    Je crois -- non, en fait, j'en suis même persuadé -- qu'il faut que tu résolve ce problème de conception avant de commencer à faire quoi que ce soit d'autre, car, plus tu attendras pour t'atteler à sa résolution, plus il te deviendra difficile de le résoudre.

    Commences peut-être déjà par faire en sorte de respecter le SRP (Single Responsability Principle ou principe de la responsabilité unique en francais) : chaque donnée, chaque type de donnée, chaque fonction ne doit s'occuper que d'une seule et unique chose:

    Le terme contrôleur est beaucoup trop vague en soi, car, à chaque fois que tu voudras ajouter une fonction pour "gérer" les éléments pris en charge par ton contrôleur, tu vas te poser la question de "mais quelle classe pourrait prendre cette fonction en charge", et que la réponse ne fera aucun doute, vu qu'elle ressemblera toujours à quelque chose comme "bah, le contrôleur est prévu à cet effet, c'est donc à lui de prendre cette nouvelle fonction en charge".

    A la longue, tu vas te retrouver avec une seule classe (ta classe Controller) qui deviendra totalement ingérable parce qu'elle exposera une quantité phénoménale de fonctions diverses et variées.

    Evites toi bien des soucis, et fait les choses correctement:

    Si les éléments que tu envisage de gérer sont polymorphes, tu dois sans doute prévoir:
    1. le moyen de les créer (une "fabrique")
    2. le moyen de les maintenir en mémoire aussi longtemps que nécessaire et de pouvoir y accéder (un "Holder" quelconque).
    3. différents moyen de les utiliser (des "manipulateurs")
    4. (le fait qu'ils soient correctement détruits quand on n'en a plus besoin : merci les pointeurs intelligents)

    Une fois que tu en seras là, tu limiteras déjà très fort les problèmes, car tu pourras te foutre pas mal de la manière dont le Holder maintient effectivement les différents éléments en mémoire : tout ce qui aura de l'importance pour toi, ce sera : sous quelle forme pourrai-je bien obtenir les éléments maintenus en mémoire.

    Et la forme la plus cohérente pour récupérer ces éléments -- pour autant que tu partes du principe qu'ils existent forcément -- c'est... sous la forme d'une référence (éventuellement constante), car ton Holder pourra prendre une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    template < /* typename Key, */ typename Value>
    class Holder{
    public:
        Value /* const */ & get(/* Key const & */){
             /* 1- rechercher l'élément associé à la clé indiquée 
              * 2- le renvoyer sous la forme d'une référence*
              */
         }
        void add(/* ... */ ){
            /* ajouter un nouvel élément à items_ */
        }
        void remove(Key const &){
            retirer l'élément associé à la clé */
        }
    private:
         std::vector<std::unique_tr<Value>> items_;
    };
    Au pire, tu te rendras compte que certains algorithmes devront -- effectivement -- parcourir l'ensemble des éléments maintenus en mémoire, indépendamment de l'ordre dans lequel ils arrivent, et qu'il serait donc intéressant de disposer des itérateurs (constant et non constant) sous une forme qui pourrait être proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    template < /* typename Key, */ typename Value>
    class Holder{
    public:
        using iterator = typename std::vector<std::unique_ptr<Value>>::iterator;
        using iterator = typename std::vector<std::unique_ptr<Value>>::const_iterator;
        Value /* const */ & get(/* Key const & */){
             /* 1- rechercher l'élément associé à la clé indiquée 
              * 2- le renvoyer sous la forme d'une référence*
              */
         }
        void add(/* ... */ ){
            /* ajouter un nouvel élément à items_ */
        }
        void remove(Key const &){
            retirer l'élément associé à la clé */
        }
        iterator begin(){
            return items_.begin();
        }
        iterator end(){
            return items_.end();
        }
        const_iterator begin() const{
            return items_.begin();
        }
        const_iterator end() const{
            return items_.end();
        }
    private:
         std::vector<std::unique_tr<Value>> items_;
    };
    Mais le SRP viendra encore une fois à te secours, car "parcourir l'ensemble des éléments d'une collection", c'est... une responsabilité différente de "manipuler un élément particulier".

    Tu pourras donc avoir une classe (template, si tu y tiens) qui ne s'occupe que ... de parcourir l'ensemble des éléments de ton Holder pour appeler à partir de chacun d'eux une fonction bien particulière, qui pourrait prendre une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <typename Elem, 
              typename Functor,
              >
    struct HolderIterator{
        void iterate(Holder<Elem> /* const & */ holder){
            for(auto & it: holder){
                Functor{}(*(it.get());
            }
        }
    };
    Ou le type que j'ai désigné sous le terme de Functor correspond ... à un foncteur spécifique à la fonction que tu veux exécuter.

    J'ai ouvert un sujet spécial pour cela, comme cela on pourra s'en tenir ici à ma question qui porte sur l'implémentation technique de ce que je veux faire, pas sur son bien-fondé !
    Excuses moi, j'ai lu cette partie du message trop tard

    De plus, C++ est un langage beaucoup trop permissif pour que l'on puisse se permettre de faire des choix dont le bien-fondé est remis en question!

    La philosophie de C++ a toujours été de se dire que "le développeur sait ce qu'il fait" et qu'il est capable de prendre des décisions "en connaissance de cause". Cela implique qu'il faut impérativement être en mesure de justifier tous les choix que nous pouvons faire sous peine de foncer dans le mur

    On peut décider de ne pas respecter les principes de conception. C++ nous donne "plus que le nécessaire" pour pouvoir le faire. Mais, chaque fois que l'on décide de faire ce genre de choix, il faut savoir que l'on prend un risque majeur, et qu'il faut donc -- très certainement -- être en mesure de justifier ce choix en termes de bénéfices (réels ou espérés) par rapport aux risques encourus.

    Prendre la décision de déroger aux principes de conception sous prétexte que "c'est autorisé par le lanage" est encore la meilleure manière de se retrouver dans une merde sans nom à brève échéance (et souvent plus tôt qu'on ne le croyait)
    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

Discussions similaires

  1. Comment créer une class en JQuery ?
    Par NEOAKIRA dans le forum jQuery
    Réponses: 2
    Dernier message: 14/05/2010, 15h16
  2. Comment créer une classe de validation avec un paramètre?
    Par Cecile5 dans le forum Windows Presentation Foundation
    Réponses: 2
    Dernier message: 10/11/2009, 10h29
  3. Réponses: 1
    Dernier message: 11/09/2007, 14h49
  4. Comment créer une classe "source d'évènements" ?
    Par gobgob dans le forum AWT/Swing
    Réponses: 2
    Dernier message: 06/07/2007, 21h15
  5. Comment créer une application Service qui lance un .exe.
    Par yosthegost dans le forum Delphi
    Réponses: 5
    Dernier message: 18/05/2006, 11h37

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