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 :

Templates : comment passer un paramètre au constructeur d'une classe utilisant un template ?


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Octobre 2005
    Messages
    35
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2005
    Messages : 35
    Par défaut Templates : comment passer un paramètre au constructeur d'une classe utilisant un template ?
    Bonjour à tous, je découvre les templates depuis hier et je m'ARRACHE les cheveux... Je vais essayer de résumer mon problème, peut-être pourrez-vous m'aider à trouver une solution ou bien m'indiquer un meilleur design. Je poste uniquement du code exemple sinon ce serait trop long à lire.

    J'ai 2 classes : Surface et Media (et demain d'autres), elle ne dérivent pas d'une classe commune et leurs constructeurs n'attendent pas les mêmes paramètres.
    J'ai besoin d'ajouter à ces classe la capacité d'interpoler des valeurs dans le temps. Au début j'ai pensé à l'héritage multiple (dériver Surface et Media d'une classe Interpolator), ça aurait fonctionné sans problème. Mais comme j'ai lu que l'héritage multiple était quelque chose de très mal, j'ai voulu essayer avec des templates.

    Donc j'ai écrit
    - une classe Interpolator
    - un template Interpolable

    Qui sont définis 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
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    struct Interpolator
    {   
       Interpolator(float* theRef) {
       }
     
       Interpolator(int* theRef) {
       }
     
       void update() {}
    };
     
    template <class T>
    struct Interpolable : public T
    {   
       vector<Interpolator> interpolators;
     
       template<typename T2> 
       void interpolate(T2* value) { 
       	Interpolator it(value);
       	interpolators.push_back(it);
       }
     
       void update() {
         T::update();
         while(interpolators.size() > 0)
         for(int i=0; i<interpolators.size(); i++) {
         	interpolators[i].update();
         }
       }
    };
    Je passe sur les détails, ça fonctionne et je l'utilise comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Interpolable<MediaVideo> *mv1 = new Interpolable<MediaVideo>;
    Là ou ça se barre en sucette c'est quand j'utilise autre chose que les constructeurs par défaut, donc si ma classe Media défini un constructeur
    J'ai essayé toute sortes de choses, la plus logique étant
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Interpolable<MediaVideo> *mv1 = new Interpolable<MediaVideo>(1);
    J'obtiens invariablement un message d'erreur à la compilation :
    templ2.cpp:96:67: error: no matching function for call to 'Interpolable<MediaVideo>::Interpolable(int)'
    Interpolable<MediaVideo> *mv1 = new Interpolable<MediaVideo>(1);
    ^
    templ2.cpp:96:67: note: candidates are:
    templ2.cpp:73:8: note: Interpolable<MediaVideo>::Interpolable()
    struct Interpolable : public T
    ^
    templ2.cpp:73:8: note: candidate expects 0 arguments, 1 provided
    templ2.cpp:73:8: note: Interpolable<MediaVideo>::Interpolable(const Interpolable<MediaVideo>&)
    templ2.cpp:73:8: note: no known conversion for argument 1 from 'int' to 'const Interpolable<MediaVideo>&'
    Je ne comprends pas ce qui lui pose problème, tout ça est totalement opaque pour moi...

    Merci de votre aide !

  2. #2
    Membre éprouvé Avatar de KsassPeuk
    Homme Profil pro
    Ingénieur Chercheur
    Inscrit en
    Juillet 2013
    Messages
    138
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur Chercheur
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2013
    Messages : 138
    Par défaut
    Lu'!

    Citation Envoyé par Paganoni Voir le message
    Au début j'ai pensé à l'héritage multiple (dériver Surface et Media d'une classe Interpolator), ça aurait fonctionné sans problème. Mais comme j'ai lu que l'héritage multiple était quelque chose de très mal, j'ai voulu essayer avec des templates.
    Ah bon ? Pourquoi ? L'héritage à outrance fait n'importe comment, c'est mal (typiquement non respect du LSP). Après si Surface ou Media "est un" Interpolator, il n'y a pas de mal à ce que ça en soit (si tu ne veux pas qu'ils sortent du même arbre d'héritage, tu peux utiliser le CRTP).

    La raison pour laquelle ton code ne passe pas est simple : la classe interpolable ne contient pas de constructeur comme celui que tu demandes. Je vois deux solutions à ton problème (d'un point de vue strictement technique), passer l'objet du type paramétrant en paramètre en transmettant la responsabilité à l'objet accueillant, ou alors rendre le constructeur template (variadique si besoin) et transmettre les paramètres au constructeur de l'objet paramétrant dans la liste d'initialisation.

    Mais après, je ne comprends pas le problème que tu veux résoudre (les interpolmachin et les trucs que tu veux faire), et peut être que c'est simplement la conception qui ne convient pas.

    Petite note : les pointeurs nus responsables de la mémoire c'est crado.

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Octobre 2005
    Messages
    35
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2005
    Messages : 35
    Par défaut
    Merci de ta réponse, j'en comprends qu'il n'y a pas de solution élégante... Les templates ne sont sans doute pas adaptés à ce que je veux faire...

    J'ai recodé mon bouzin avec l'héritage multiple et c'est :
    - plus simple à écrire
    - plus joli à lire
    - et ça fonctionne

    Oui pour les pointeurs nus, mais la classe Interpolator doit faire varier dans le temps des valeurs de types différents (variables membres de classes Media ou Surface, par ex.) et le truc le plus sympa que j'ai trouvé c'est de stocker des pointeurs vers les variables en question et de les incrémenter ou décrémenter au fil du temps dans la classe Interpolator.

    Mais pour pas faire des type cast dangeureux ou incohérents j'ai fait 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
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
     
    struct iterValue {
    	enum it_type { t_int, t_float, t_double };
    	int v_type;
    		float * v_f;
    		int * v_i;
    		double * v_d;
    	iterValue(float *value) {
    		v_f = value;
    		v_type = t_float;
    	}
    	iterValue(int *value) {
    		v_i = value;
    		v_type = t_int;
    	}
    	iterValue(double *value) {
    		v_d = value;
    		v_type = t_double;
    	}
    };
     
    void update() {
         cout << "in interpolator update" << endl;
         for(int i=0; i<inter_values.size(); i++) {
         	switch(inter_values[i].v_type) {
         		case iterValue::t_int :
         			(*inter_values[i].v_i)++;
         			break;
         		case iterValue::t_float :
         			(*inter_values[i].v_f)++;
         			break;
         		case iterValue::t_double :
         			(*inter_values[i].v_d)++;
         			break;
         	}     	
         }
       }
    J'aurai sans doute pu arriver au même résultat en utilisant des références au lieu des pointeurs, mais j'ai bien les pointeurs. Je trouve ça moins ambigu à lire.

  4. #4
    Membre éprouvé Avatar de KsassPeuk
    Homme Profil pro
    Ingénieur Chercheur
    Inscrit en
    Juillet 2013
    Messages
    138
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur Chercheur
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2013
    Messages : 138
    Par défaut
    Citation Envoyé par Paganoni Voir le message
    J'ai recodé mon bouzin avec l'héritage multiple et c'est :
    - plus simple à écrire
    - plus joli à lire
    - et ça fonctionne

    Oui pour les pointeurs nus, mais la classe Interpolator doit faire varier dans le temps des valeurs de types différents (variables membres de classes Media ou Surface, par ex.) et le truc le plus sympa que j'ai trouvé c'est de stocker des pointeurs vers les variables en question et de les incrémenter ou décrémenter au fil du temps dans la classe Interpolator.
    Euh, ça me paraît bizarre ton affaire, on peut voir à quoi ressemble ton code actuel ? Parce que si tu as des fonctions qui agissent différemment, c'est juste des points de variations (et donc du virtual). Donc, il n'y a pas de raison que tu passes sur un truc comme ton IterValue, qui n'est pas propre et pas extensible.

    Citation Envoyé par Paganoni Voir le message
    J'aurai sans doute pu arriver au même résultat en utilisant des références au lieu des pointeurs, mais j'ai bien les pointeurs. Je trouve ça moins ambigu à lire.
    J'ai pas râlé à propos des pointeurs, j'ai râlé à propos de pointeurs nus responsables de la mémoire. Par ailleurs, les références sont sémantiquement bien plus fortes que les pointeurs et il n'y a pas de raison d'utiliser un pointeur à un endroit où l'on attends forcément de trouver un objet.

  5. #5
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Hello

    Je vais répondre à plusieurs points. Tout d'abord le premier : tu aurais pu obtenir l'effet recherché dans ta question initiale avec des templates variadiques :

    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
    #include <string>
    #include <utility>
     
    template <typename T> struct Interpolable : public T {
      template <typename ... A> Interpolable(A&& ... args) : T(std::forward<A>(args) ...) {}
    };
     
    struct A {
      A(int) {}
    };
     
    struct B {
      B(std::string const&) {};
    };
     
    int main() {
      Interpolable<A>(1);
      Interpolable<B>("Roger");
      return 0;
    }
    Il existe donc bien une solution élégante pour le faire.

    La solution par héritage dans l'autre sens est bien aussi, surtout si tu comptes à l'avenir leur ajouter d'autre propriétés que l'interpolation. Ce qui est très dommage dans ta solution finale, c'est ton switch sur le type pour utiliser le bon, car tu testes le type au runtime alors que tu pourrais le faire compile-time, c'est justement le type de services que rendent les templates !

    Pour un exemple simpliste:

    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
    #include <string>
    #include <utility>
     
    template <typename T> class Interpolable {
      T value_;
     
     public:
      void update(T value) {
        value = value_;
      }
    };
     
    struct A  : public Interpolable<float> {};
     
    struct B : public Interpolable<double> {};
    Pour les pointeurs, pas d'excuses, surtout si tu adaptes ta solution pour choisir le bon type avec un template.

  6. #6
    Membre averti
    Profil pro
    Inscrit en
    Octobre 2005
    Messages
    35
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2005
    Messages : 35
    Par défaut
    Merci pour ta réponse jb, la syntaxe est un peu raide à piger vu mon niveau de compréhension des templates mais je vais expérimenter dans un source de test (comme je l'ai fait jusqu'à présent).

    Quoi qu'il en soit, j'ai finalement pluggé mon interpolateur dans l'appli que je développe sans douleur, en utilisant l'héritage multiple. Pour les classes existantes je n'ai eu quasiment aucune modification.
    Donc je suis content !

    Ok, je sais que mes bidouilles pour déterminer le bon type sont pas jolies mais je pense qu'elles sont safes.

    Voici interpolation.h

    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
     
    #include <iostream>
    #include <sys/time.h>
    #include "ofMain.h"
     
    class TAInterpoledValue {
    	enum it_type { t_int, t_float };
     
    	int v_type;
    	float *v_f;
    	int *v_i;
     
    	unsigned long long start_time;
    	float startValue;
    	float endValue;
    	int duration;
    	int animationType;
     
    public:
    	enum animation_types {linear, quad_in, quad_out};
     
    	TAInterpoledValue(float *value);
    	TAInterpoledValue(int *value);
     
    	void setToValue(float v);
    	void setToEndValue();
    	float getValue();
     
    	float time_linear(float time);
    	float time_quadraticIn(float time);
    	float time_quadraticOut(float time);
     
    	void setup(int duration, float final, int atype);
    	void setup(int duration, float start, float final, int atype);
    	bool update();
    };
     
    class TAInterpolator
    {
    	vector <TAInterpoledValue> inter_values;
     
    public:
    	template <typename T2>
    	void interpolate(T2 *value, int duration, float final, int atype = 0) {
    		TAInterpoledValue v(value);
     
    		v.setup(duration, final, atype);
    		inter_values.push_back(v);
    	}
     
    	template <typename T2>
    	void interpolate(T2 *value, int duration, float start, float final, int atype = 0) {
    		TAInterpoledValue v(value);
     
    		v.setup(duration, start, final, atype);
    		inter_values.push_back(v);
    	}
     
    	void update() {
    		for (int i = 0; i < inter_values.size(); i++) {
    			if (inter_values[i].update()) {
    				inter_values.erase(inter_values.begin() + i);
    			}
    		}
    	}
    };
    et interpolation.cpp

    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
     
    #include "TAInterpolation.h"
     
    TAInterpoledValue::TAInterpoledValue(float *value) {
    	v_f = value;
    	v_type = t_float;
    }
     
    TAInterpoledValue::TAInterpoledValue(int *value) {
    	v_i = value;
    	v_type = t_int;
    }
     
    void TAInterpoledValue::setToValue(float v) {
    	switch (v_type) {
    		case t_int:
    			(*v_i) = v;
    			break;
     
    		case t_float:
    			(*v_f) = v;
    			break;
    	}
    }
     
    void TAInterpoledValue::setToEndValue() {
    	setToValue(endValue);
    }
     
    float TAInterpoledValue::getValue() {
    	switch (v_type) {
    		case t_int:
    			return *v_i;
    			break;
     
    		case t_float:
    			return *v_f;
    			break;
    	}
    }
     
    float TAInterpoledValue::time_linear(float time) {
    	return time;
    }
     
    float TAInterpoledValue::time_quadraticIn(float time) {
    	return time * time;
    }
     
    float TAInterpoledValue::time_quadraticOut(float time) {
    	return time * (2 - time);
    }
     
    void TAInterpoledValue::setup(int duration, float final, int atype) {
    	this->start_time = ofGetElapsedTimeMillis();
    	this->duration = duration * 1000;
    	this->startValue = getValue();
    	this->endValue = final;
    	this->animationType = atype;
    }
     
    void TAInterpoledValue::setup(int duration, float start, float final, int atype) {
    	this->startValue = start;
    	setup(duration, final, atype);
    }
     
    bool TAInterpoledValue::update() {
    	unsigned long long elapsed = ofGetElapsedTimeMillis() - start_time;
    	if (elapsed >= duration) {
    		setToEndValue();
    		return true;
    	}
    	else {
    		float time = (float)elapsed / (float)this->duration;
    		float time_computed;
    		switch (this->animationType) {
    			case linear:
    			default:
    				time_computed = time_linear(time);
     
    			case quad_in:
    				time_computed = time_quadraticIn(time);
     
    			case quad_out:
    				time_computed = time_quadraticOut(time);
    		}
    		float v = this->startValue + (this->endValue - this->startValue) * time_computed;
    		setToValue(v);
    		return false;
    	}
    }
    Du coup du côté des classes Media et Surface je peux écrire
    interpolate(&opacity, 10, 1.0f);
    interpolate(&volume, 10, 0.0f);

    Qu'il s'agisse de floats (opacité) ou d'int (volume)

    Tout ce qui a changé dans mes deux classes c'est
    - hériter de public TAInterpolator
    - ajouter TAInterpolator::update() dans les méthodes update() de Surface et Media

    Et ça roule !!!

    Je vais essayer de me creuse la tête pour que TAInterpoledValue utilise un template pour virer ce qui existe actuellement, et je garderai cependant le principe de l'héritage multiple...

  7. #7
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Hello

    En lisant ton code, je comprend mieux.

    Déjà, sur l'histoire des pointeurs, peut-importe la justification, le fait que l'interpolateur utilise des pointeurs devrait rester un détail d'implémentation. En d'autres termes, dans les fonctions membres interpolate, tu passes en argument une référence et tu prend l'adresse, si tu en as effectivement besoin, dans l'implémentation. Ce n'est pas à l'utilisateur de l'interpolateur d'être impacté par ça.

    Plusieurs questions, pour moi, se posent:

    1. Est-ce que dans un seul objet, certaines variables interpolées seront entières et d'autres flottantes ?
    2. Est-il vraiment utile de supprimer des éléments du tableau ?
    3. L'héritage rend-t-il un service ?


    Point 1

    Si dans un objet, un seul type de donnée est interpolé, alors l'interpolateur doit être templaté pour n'accepter et traiter que ce type. Si dans un seul objet, certaines variables qui doivent être interpolées sont entières et d'autres flottantes, je ne vois pas l'intérêt de rajouter un mécanisme de typage dynamique : il devrait y avoir un tableau pour les entiers et un autre pour les flottants, le tout géré avec des surcharges et pas avec des fonctions membres templates.

    Point 2

    Je vois que tu supprimes des éléments dans le tableau. Tout d'abord, ton code ne fonctionne pas bien, voir l'idiome erase/remove. Et deuxièmement, si lorsque tu arrives à l'instant t qui signifie la fin de l'interpolation, à quoi cela sert-il de faire un setToEndValue() si c'est pour la supprimer juste après ? Si tu dois effectivement supprimer ces valeurs, est-ce que ça ne veut pas dire que ton objet est en fin de vie, et que par conséquent, il te suffit le laisser le soin au destructeur de vider le vecteur ? Il y a un problème de conception ici.

    J'irais même plus loin en disant que selon moi, tu devrais utiliser des tableaux de tailles fixes (std::array), taille qui est passée en paramètre template par la classe utilisatrice, car la classe en question sait à l'avance combien de variables seront interpolées.

    Point 3

    Celui-ci risque de te chiffonner . Ce que je constate, c'est qu'il n'y a pas de fonctions membres publiques d'accès au données résultantes de l'interpolation. Il n'y a que interpolate et update. Soit ces accesseurs publics ne sont pas publiés pour limiter le code sur le forum (dans ce cas bien vu, mais ajoute un petit commentaire dans le code qui le précise ), soit il n'y pas d'intérêt à utiliser de l'héritage ici. Dans ce dernier cas, il vaut mieux travailler par composition, c'est à dire, ajoute une donnée membre de type Interpolator dans les classes qui s'en servent.



    Il y a encore un peu de boulot, mais tu ne regretteras d'avoir été attentif à ta conception en début de projet

Discussions similaires

  1. Réponses: 15
    Dernier message: 07/06/2011, 22h42
  2. Comment passer des paramètre a OpenRecordset
    Par molarisapa dans le forum Access
    Réponses: 2
    Dernier message: 09/03/2006, 17h14
  3. Réponses: 3
    Dernier message: 28/02/2006, 08h43
  4. [VB6] Comment passer un paramètre à un vbs depuis du vb6
    Par durnambule dans le forum VBScript
    Réponses: 2
    Dernier message: 27/09/2005, 10h46
  5. Réponses: 7
    Dernier message: 30/12/2004, 12h01

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