OVH Cloud OVH Cloud

Héritage multiple

8 réponses
Avatar
Michael
Bonjour à tous,

je réalise en ce moment un petit soft de montage vidéo, et je voulais
savoir ce que vous pensiezdes classes suivantes, représentant les
différents objets pouvant être insérés dans 1 projet:

const enum _type_sequence { ms_Video, ms_Image, ms_Titre, ms_Transition
};

//La classe de base
class Montage_Sequence
{
public:
mutable AnsiString nom_sequence;
mutable long index_ds_timeline; //Position dans le projet
mutable Time_Period temps; //Temps de la séquence
int left; //Coordonnées de la vignette dans le projet
int top;
_type_sequence type_sequence;

__fastcall Montage_Sequence(const _type_sequence & type)
: type_sequence(type) {}

__fastcall ~Montage_Sequence() {}

bool operator==(const long index) const
{ return index == index_ds_timeline; }

bool operator<(const Montage_Sequence & compar) const
{ return index_ds_timeline < compar.index_ds_timeline; }
};

//Tous les medias présents physiquement sur le disque
struct Montage_Track
{
WideString path;
};

//Les médias pouvant être prévisualisés
class Montage_Preview
{
public:
TJPEGImage * preview;

__fastcall Montage_Preview() {}
};

//Vidéos
class Montage_Video : public Montage_Sequence, public Montage_Track,
public Montage_Preview
{
public:
mutable float rate;

__fastcall Montage_Video()
: Montage_Sequence(ms_Video) {}
};

//Images
class Montage_Image : public Montage_Sequence, public Montage_Track,
public Montage_Preview
{
public:
__fastcall Montage_Image()
: Montage_Sequence(ms_Image) {}
};

//Titres
class Montage_Titre : public Montage_Sequence, public Montage_Preview
{
public:
AnsiString texte;
TColor background;
TColor foreground;

__fastcall Montage_Titre()
: Montage_Sequence(ms_Titre) {}
};

//Transitions
class Montage_Transition : public Montage_Sequence
{
public:
__fastcall Montage_Transition()
: Montage_Sequence(ms_Transition) {}
};

Qu'est-ce que vous pensez de cette organisation?
Ne dois-je pas faire de Montage_Track et de Montage_Preview des classes
virtuelles ou abstraites, vu qu'elles ne doivent pas être instanciées en
tant que telles?

Merci d'avance

Mike

8 réponses

Avatar
Olivier Azeau
Michael wrote:
Bonjour à tous,

je réalise en ce moment un petit soft de montage vidéo, et je voulais
savoir ce que vous pensiezdes classes suivantes, représentant les
différents objets pouvant être insérés dans 1 projet:

const enum _type_sequence { ms_Video, ms_Image, ms_Titre, ms_Transition
};
Cet enum est-il nécessaire puisque l'on voit par la suite qu'il existe

une classe pour chacun des éléments ?


//La classe de base
class Montage_Sequence
{
public:
mutable AnsiString nom_sequence;
mutable long index_ds_timeline; //Position dans le projet
mutable Time_Period temps; //Temps de la séquence
int left; //Coordonnées de la vignette dans le projet
int top;
Probablement un peu trop de trucs publics par ici qui vont induire un

couplage fort avec les utilisations de cette classe.

_type_sequence type_sequence;
j'imagine que type de sequence n'a pas de raison d'être modifié après la

création de l'objet ?
Un méthode virtuelle pure ne serait-elle pas une meilleure solution ?


__fastcall Montage_Sequence(const _type_sequence & type)
: type_sequence(type) {}

__fastcall ~Montage_Sequence() {}
classe de base => destructeur public virtuel ou protected non virtuel ?



bool operator==(const long index) const
{ return index == index_ds_timeline; }

bool operator<(const Montage_Sequence & compar) const
{ return index_ds_timeline < compar.index_ds_timeline; }
};

//Tous les medias présents physiquement sur le disque
struct Montage_Track
{
WideString path;
};

//Les médias pouvant être prévisualisés
class Montage_Preview
{
public:
TJPEGImage * preview;

__fastcall Montage_Preview() {}
};

//Vidéos
class Montage_Video : public Montage_Sequence, public Montage_Track,
public Montage_Preview
{
public:
mutable float rate;

__fastcall Montage_Video()
: Montage_Sequence(ms_Video) {}
};

//Images
class Montage_Image : public Montage_Sequence, public Montage_Track,
public Montage_Preview
{
public:
__fastcall Montage_Image()
: Montage_Sequence(ms_Image) {}
};

//Titres
class Montage_Titre : public Montage_Sequence, public Montage_Preview
{
public:
AnsiString texte;
TColor background;
TColor foreground;

__fastcall Montage_Titre()
: Montage_Sequence(ms_Titre) {}
};

//Transitions
class Montage_Transition : public Montage_Sequence
{
public:
__fastcall Montage_Transition()
: Montage_Sequence(ms_Transition) {}
};

Qu'est-ce que vous pensez de cette organisation?
Ne dois-je pas faire de Montage_Track et de Montage_Preview des classes
virtuelles ou abstraites, vu qu'elles ne doivent pas être instanciées en
tant que telles?


Si le besoin est juste d'empêcher une instanciation directe de
Montage_Track ou Montage_Preview, alors un destructeur protected et un
héritage privé devraient suffire.

La question importante est de savoir si ces 2 classes ne réprésentent
que des implémentations (path vers un fichier de stockage, image de
previsualisation, ...) ou est-ce qu'elles ne représenteraient pas aussi
un comportement (capacité à se stocker physiquement accèdée par un
gestionnaire de media, capacité de visualisation accèdée par une fenêtre
de timeline, ...) auquel cas il serait peut être plus utile d'exposer
des interfaces plutôt que des membres d'implémentation.

Avatar
Michael
const enum _type_sequence { ms_Video, ms_Image, ms_Titre,
ms_Transition };
Cet enum est-il nécessaire puisque l'on voit par la suite qu'il existe

une classe pour chacun des éléments ?


OK, c'est vrai

//La classe de base
class Montage_Sequence
{
public:
mutable AnsiString nom_sequence;
mutable long index_ds_timeline; //Position dans le projet
mutable Time_Period temps; //Temps de la séquence
int left; //Coordonnées de la vignette dans le projet
int top;
Probablement un peu trop de trucs publics par ici qui vont induire un

couplage fort avec les utilisations de cette classe.


C'est juste pour l'exemple, ça changera par la suite...


_type_sequence type_sequence;
j'imagine que type de sequence n'a pas de raison d'être modifié après

la création de l'objet ?
Un méthode virtuelle pure ne serait-elle pas une meilleure solution ?


OK


Qu'est-ce que vous pensez de cette organisation?
Ne dois-je pas faire de Montage_Track et de Montage_Preview des
classes virtuelles ou abstraites, vu qu'elles ne doivent pas être
instanciées en tant que telles?


Si le besoin est juste d'empêcher une instanciation directe de
Montage_Track ou Montage_Preview, alors un destructeur protected et un
héritage privé devraient suffire.


Le fait de rendre le destructeur protected suffit pour empêcher une
instantiation directe impossible?

La question importante est de savoir si ces 2 classes ne réprésentent
que des implémentations (path vers un fichier de stockage, image de
previsualisation, ...) ou est-ce qu'elles ne représenteraient pas
aussi un comportement (capacité à se stocker physiquement accèdée par
un gestionnaire de media,


La je vois pas de quoi tu veux parler

capacité de visualisation accèdée par une
fenêtre de timeline, ...) auquel cas il serait peut être plus utile
d'exposer des interfaces plutôt que des membres d'implémentation.


Oui, j'ai pensé pour la classe Montage_Preview à une fonction virtuelle
pure redéclarée dans les classes filles afin qu'elles gérent elles-même
l'extraction du preview (depuis une image, depuis un instant T de la
vidéo...)


Avatar
Olivier Azeau
Michael wrote:
Qu'est-ce que vous pensez de cette organisation?
Ne dois-je pas faire de Montage_Track et de Montage_Preview des
classes virtuelles ou abstraites, vu qu'elles ne doivent pas être
instanciées en tant que telles?


Si le besoin est juste d'empêcher une instanciation directe de
Montage_Track ou Montage_Preview, alors un destructeur protected et un
héritage privé devraient suffire.



Le fait de rendre le destructeur protected suffit pour empêcher une
instantiation directe impossible?


Pas directement.
Dans le cas d'une allocation sur la pile, pas de problème : l'appel au
destructeur généré par le compilo provoque une erreur de compil quand le
destructeur est protected.
Dans le cas d'une allocation dans le tas (new/delete), il faut être ok
au niveau RAII pour ne pas avoir de new sans delete (auquel cas le
destructeur protected ne protège pas contre l'instanciation)

La question importante est de savoir si ces 2 classes ne réprésentent
que des implémentations (path vers un fichier de stockage, image de
previsualisation, ...) ou est-ce qu'elles ne représenteraient pas
aussi un comportement (capacité à se stocker physiquement accèdée par
un gestionnaire de media,



La je vois pas de quoi tu veux parler


Tout comme tu indiques juste après que tu envisages du polymorphisme sur
Montage_Preview, tu peux en avoir besoin aussi au niveau de
Montage_Track mais ça dépend de ce que fait ton appli...
On peut imaginer une représentation physique qui ne soit pas un simple
path : par exemple, on peut avoir découpé virtuellement une gros fichier
video au niveau de chaque scéne.

capacité de visualisation accèdée par une
fenêtre de timeline, ...) auquel cas il serait peut être plus utile
d'exposer des interfaces plutôt que des membres d'implémentation.



Oui, j'ai pensé pour la classe Montage_Preview à une fonction virtuelle
pure redéclarée dans les classes filles afin qu'elles gérent elles-même
l'extraction du preview (depuis une image, depuis un instant T de la
vidéo...)


A mon avis, au niveau C++, ce qui est important c'est de différencier un
héritage d'interface obligatoirement public d'un héritage
d'implémentation qui gagne à être privé s'il n'inclut pas d'aspect
comportemental vis à vis de l'extérieur de la hiérarchie.

Après on peut discuter de l'implémentation par héritage vs
implémentation par composition mais ceci est une autre histoire (voire
un autre troll ?)



Avatar
Michael
const enum _type_sequence { ms_Video, ms_Image, ms_Titre,
ms_Transition };
Cet enum est-il nécessaire puisque l'on voit par la suite qu'il existe

une classe pour chacun des éléments ?


Je voudrais revenir sur ce point:

en l'état actuel des choses, je voudrais laisser cet enum qui me permet de
savoir de quel type est la séquence...

En effet, je conserve toutes mes séquences dans un vecteur:
std::vector<Montage_Sequence *> liste_sequences;

Par moments je suis obligé de parcourir le vecteur, et les actions sont
différentes selon le type de séquence...

Ex:

for (ITE ite = liste_sequences.begin(); ite != liste_sequences.end();
++ite)
{
if ((*ite)->type_sequence == ms_Image)
{
Montage_Image * img = dynamic_cast<Montage_Image*>(*ite);
if (img)
Edit_Video->Add_Screen(-1,img->Get_Path(),img->Get_Temps
().fin);
}
else if ((*ite)->type_sequence == ms_Video)
{
Montage_Video * vid = dynamic_cast<Montage_Video*>(*ite);
if (vid)
Edit_Video->Add_VideoTrack(-1,vid->Get_Path(),vid->
Get_Temps(),vid->Get_Rate());
}
}

C'est la seule possibilité à laquelle j'ai pensé...

Y a-t-il mieux? Comme par exemple ceci:

for (ITE ite = liste_sequences.begin(); ite != liste_sequences.end();
++ite)
{
Montage_Image * img = dynamic_cast<Montage_Image*>(*ite);
Montage_Video * vid = dynamic_cast<Montage_Video*>(*ite);

if (img != NULL)
{
Edit_Video->Add_Screen(-1,img->Get_Path(),img->Get_Temps
().fin);
}
else if (vid != NULL)
{
Edit_Video->Add_VideoTrack(-1,vid->Get_Path(),vid->Get_Temps
(),vid->Get_Rate());
}
}

Merci d'avance...

Mike


Avatar
xavier
Michael a dis le 10/01/2005 15:30:
Y a-t-il mieux? Comme par exemple ceci:

for (ITE ite = liste_sequences.begin(); ite != liste_sequences.end();
++ite)
{
Montage_Image * img = dynamic_cast<Montage_Image*>(*ite);
Montage_Video * vid = dynamic_cast<Montage_Video*>(*ite);


Juste pour information, l'opérateur dynamic_cast<> lance une exception
(std::bad_cast) si le pointeur ne correspond pas à la classe demandée.

La meilleure solution reste, à mon avis :

class Montage_Sequence {
public:
virtual void FaitQuelqueChose(Video_Edition * Edit_Video) = 0;
};

definir cette fonction pour chaque classe héritant de Montage_Sequence,

ensuite, ta boucle devient simplement

for (ITE ite = liste_sequences.begin(); ite != liste_sequences.end();
++ite)
(*ite)->FaitQuelqueChose(Edit_Video);


xavier

Avatar
Michael
La meilleure solution reste, à mon avis :

class Montage_Sequence {
public:
virtual void FaitQuelqueChose(Video_Edition * Edit_Video) = 0;
};

definir cette fonction pour chaque classe héritant de Montage_Sequence,

ensuite, ta boucle devient simplement

for (ITE ite = liste_sequences.begin(); ite != liste_sequences.end();
++ite)
(*ite)->FaitQuelqueChose(Edit_Video);


OK, merci, je vais tester ça...

Avatar
Christophe Lephay
"xavier" a écrit dans le message de news:
41e29456$0$19578$
Juste pour information, l'opérateur dynamic_cast<> lance une exception
(std::bad_cast) si le pointeur ne correspond pas à la classe demandée.


Euh, il renverrait pas un pointeur nul, plutôt ?

Avatar
xavier
Christophe Lephay wrote:
Euh, il renverrait pas un pointeur nul, plutôt ?


Tout à fait.

L'habitude de ne travailler qu'avec des références... J'avais oublié que
pour dynamic_cast<> avait un fonctionnement différent sur des pointeurs.

xavier