Ajout de methodes / spécialisation de templates

Le
Vincent Lascaux
Bonjour,

Je travaille sur un projet de traitement d'image. J'ai une classe
Image template sur le type de pixel. La classe propose un certain
nombre de fonctionnalités communes à tous les types (copier, convertir
vers un autre format de pixel, tracer des traits, des cercles). Il
y a toutefois certaines opérations qui n'ont de sens que pour certains
types de pixels : par exemple calculer le gradient ne peut se faire
(ou du moins avec ma bibliotheque) que sur des images en niveau de
gris.

J'ai imaginé avoir un truc comme ca :
template<class PixType>
class Image
{
public:
void Gradient()
{
const Image<PixGray*> compileTimeError = this;
}
};
mais c'est pas vraiment satisfaisant d'avoir la fonction dans toutes
les classes avec un truc qui compile pas si on tente de l'appeler.

J'ai aussi pensé faire comme ca :

template<class PixType>
class ImageBase
{
public:
// Fonctions communes à tous les types
};

template<class PixType>
class Image : public ImageBase<PixType>
{ };

template<>
class Image<PixGray> : public ImageBase<PixGray>
{
public:
// Fonctions juste pour PixGray
};

mais maintenant je voudrais ajouter une fonction (la même, avec la
même implémentation) à PixRGB, PixYCrCb, et PixHSV (pour info c'est la
séparation en 3 images PixGray pour les channels), et les deux
méthodes précédentes atteignent leur limites.

Est ce qu'il y a une façon de faire ce que je veux : une sorte de truc
comme ca :
template<class PixType>
class Image
{
public:
<if PixType == PixGray> // Comment ca s'écrit ?
void Gradient()
{
//
}
</if>
<if PixType in { PixRGB, PixYCrCb, PixHSV } >
// ou mieux: <if PixType::cChannels == 3>
void SplitChannels(/* */)
{
//
}
</if>
};

Merci,

--
Vincent
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Fabien LE LEZ
Le #305715
On Mon, 09 Apr 2007 19:49:48 -0700, Vincent Lascaux

template<class PixType>
class Image
{
public:
void Gradient()


Pourquoi la fonction Gradient() devrait-elle être membre ?

J'imagine que cette fonction lit des pixels, puis modifie des pixels.
Et je suppose que la classe a déjà des fonctions publiques qui
permettent de lire et modifier des pixels.

En d'autres termes, Gradient() peut très bien être une fonction libre,
qui fera son boulot en utilisant l'interface publique de Image<>.

template<class PixType>
class Image
{ ... };

void Gradient (Image<PixGray>& image);


Supposons maintenant que tu souhaites rendre la fonction Gradient()
template, pour pouvoir accepter certains types et pas d'autres.
On peut faire ça, indirectement, très facilement :


/// Image.h

void Gradient (Image<PixGray>& image);
void Gradient (Image<AutreType>& image);

/// Image.cpp

template<class PixType> void DoGradient (Image<PixType>& image)
{
// Le code "réel" ici
}

void Gradient (Image<PixGray>& image) { DoGradient (image); }
void Gradient (Image<AutreType>& image) { DoGradient (image); }

Comme la fonction template DoGradient<> est visible uniquement de ton
code (i.e. dans le .cpp), tu es assuré qu'on ne l'appellera qu'avec
les types qui vont bien.

Vincent Lascaux
Le #305714
Pourquoi la fonction Gradient() devrait-elle être membre ?


Hum... Effectivement, ca résoud le problème...

Tracer la frontiere entre méthode et fonctions "globales" est assez
flou (ou du moins me semble assez flou).

Gradient est une opération qui s'effectue sur une image, c'est pour
ca que je voulais l'avoir comme une méthode de la classe.

Est ce que Fill(const PixType& color) qui remplit l'image avec une
valeur constante devrait faire parti de la classe Image ou pas ?

Bref, tu serais pour que Image soit très très minimaliste
(constructeur, destructeur, resize et get/set pixel), et que tout le
reste soit des méthodes globales ? C'est une façon de voir les choses,
je suis pas sur que ce soit la plus OO qu'il soit.

--
Vincent

Fabien LE LEZ
Le #305713
On Mon, 09 Apr 2007 22:24:23 -0700, Vincent Lascaux :

Note en passant : je déconseille fortement l'usage du mot "méthode"
pour désigner une fonction, au moins dans le cadre du C++. En effet,
il ne semble pas y avoir de consensus sur la définition de ce mot.
Les deux définitions les plus courantes sont "fonction membre" et
"fonction membre virtuelle", mais tu sembles en avoir une troisième,
puisque tu emploies ce mot pour désigner une fonction libre (i.e. non
membre d'une classe).

Tracer la frontiere entre méthode et fonctions "globales" est assez
flou (ou du moins me semble assez flou).


Une fonction membre a accès aux membres privés. Il me semble qu'une
fonction qui n'a pas besoin d'y avoir accès ne devrait pas être
membre.

Par ailleurs, une fonction libre peut être séparée de la classe : si
tu en as beaucoup, tu peux les mettre dans un .h séparé, et n'inclure
ce .h que dans les .cpp qui en ont besoin.
Par exemple, si un .cpp a besoin de la classe Image mais pas de la
fonction Gradient, il peut inclure le .h qui contient Image, mais pas
celui qui contient Gradient -- ça réduit d'autant les ressources
nécessaires pour compiler, et ça rend le code plus simple à
comprendre.

Cf aussi l'article de Sutter à ce sujet :

Bref, tu serais pour que Image soit très très minimaliste
(constructeur, destructeur, resize et get/set pixel), et que tout le
reste soit des méthodes globales ? C'est une façon de voir les choses,
je suis pas sur que ce soit la plus OO qu'il soit.


Il me semble que si, au contraire : les variables membres sont
privées, et le moins possible de fonctions y ont accès.
Si une fonction n'a pas besoin d'avoir accès aux membres privés, il ne
faut pas le lui donner.

Ajoutons également que la POO n'est jamais qu'un paradigme parmi
d'autres. En d'autres termes, un outil, qu'il faut utiliser quand on
en a besoin.

Franck Branjonneau
Le #305712
Vincent Lascaux
Je travaille sur un projet de traitement d'image. J'ai une classe
Image template sur le type de pixel. La classe propose un certain
nombre de fonctionnalités communes à tous les types (copier, convertir
vers un autre format de pixel, tracer des traits, des cercles...). Il
y a toutefois certaines opérations qui n'ont de sens que pour certains
types de pixels : par exemple calculer le gradient ne peut se faire
(ou du moins avec ma bibliotheque) que sur des images en niveau de
gris.

J'ai aussi pensé faire comme ca :

template<class PixType>
class ImageBase
{
public:
// Fonctions communes à tous les types
};

template<class PixType>
class Image : public ImageBase
{ };

template<>
class Image : public ImageBase
{
public:
// Fonctions juste pour PixGray
};


Je lis Image< PixGray > ici.

mais maintenant je voudrais ajouter une fonction (la même, avec la
même implémentation) à PixRGB, PixYCrCb, et PixHSV (pour info c'est la
séparation en 3 images PixGray pour les channels), et les deux
méthodes précédentes atteignent leur limites.


La proposition de Fabien me semble pertinente.

Est ce qu'il y a une façon de faire ce que je veux : une sorte de truc
comme ca :
template<class PixType>
class Image
{
public:
<if PixType == PixGray> // Comment ca s'écrit ?
void Gradient()
{
//...
}
</if>
<if PixType in { PixRGB, PixYCrCb, PixHSV } >
// ou mieux: <if PixType::cChannels == 3>
void SplitChannels(/* ... */)
{
// ...
}
</if>
};


Utiliser un type de base ayant plus de paramètres et spécialiser selon la
valeur de ces parmètres :

template< typename _Pixel >
struct BasicBaseImage {

// Common interface
};

template< typename _Pixel, bool _hasGradient,
int _colorChannels >
struct BasicImage;

template<>
template< typename _Pixel, int _colorChannels >
struct BasicImage< _Pixel, true, _colorChannels >
public BasicBaseImage< _Pixel > {

// typedef_s + using_s from BasicBaseImage< _Pixel >

void gradient() const;
};

template<>
template< typename _Pixel, bool _hasGradient >
struct BasicImage< _Pixel, _hasGradient, 3 >
public BasicBaseImage< _Pixel > {

// typedef_s + using_s from BasicBaseImage< _Pixel >

void splitChannels() const;
};

template< typename _Pixel >
struct Image:
public BasicImage< _Pixel, Pixel::hasGradient, Pixel::colorChannels > {

// typedef_s + using_s from BasicBaseImage< _Pixel >

// Can't use using BasicImage<>::splitChannels nor BasicImage<>::gradient ?
// (lazy instanciation)
// Must be qualified.
};

Tu vois le problème : croissance exponentielle du nombre de spécialisations
explicites -- ici il en manque.


Sur le même principe (sans les inconvénients) mais plus lourd ?

template< typename _Pixel >
struct BasicBaseImage {

// Common interface
};

template< typename _Pixel >
struct BasicImage:
public BasicBaseImage< _Pixel >,
public Gradient< Pixel::hasGradient >,
public SplitChannel< Pixel::colorChannels > {

// typedef_s + using_s from BasicBaseImage< _Pixel >
using Gradient< Pixel::hasGradient >::gradient;
using SplitChannel< Pixel::colorChannels >::splitChannel;
};

template< bool _hasGradient >
struct Gradient {

// Not implemented
void gradient() const;
};

template<>
struct Gradient< true > {

void gradient() const;
};

template< int _colorChannels >
struct SplitChannel {

// Not implemented
void splitChannel() const;
};

template<>
struct SplitChannel< 3 > {

void splitChannel() const;
};

--
Franck Branjonneau

Mathias Gaunard
Le #305672

Est ce qu'il y a une façon de faire ce que je veux : une sorte de truc
comme ca :
template<class PixType>
class Image
{
public:
<if PixType == PixGray> // Comment ca s'écrit ?
void Gradient()
{
//...
}
</if>


Ce genre de choses peut se faire par exemple avec SFINAE.

typename enable_if<
is_same<PixType, PixGray>,
void>::type
Gradient()
{
}


<if PixType in { PixRGB, PixYCrCb, PixHSV } >
// ou mieux: <if PixType::cChannels == 3>
void SplitChannels(/* ... */)
{
// ...
}
</if>


typename enable_if<
or_<
is_same<PixType, PixRGB>,
or_<
is_same<PixType, PixYCrCb>,
is_same<PixType, PixHSV>

,
void>::type

SplitChannels(/* ... */)
{
}

typename enable_if<
contains<
vector<PixRGB, PixYCrCb, PixHSV>,
PixType
,
void>::type

SplitChannels(/* ... */)
{
}

typename enable_if<
PixType::cChannels == 3,
void>::type
SplitChannels(/* ... */)
{
}

Loïc Joly
Le #305670
template<class PixType>
class Image
{
public:
void Gradient()
{
const Image<PixGray*> compileTimeError = this;
}
};
mais c'est pas vraiment satisfaisant d'avoir la fonction dans toutes
les classes avec un truc qui compile pas si on tente de l'appeler.


Une fonction template n'est instanciée que si elle est utilisée. Donc ta
fonction ne sera que dans des classes où elle est utilisable. Et avoir
un truc qui ne compile pas me semble le meilleur niveau d'erreur qu'on
puisse obtenir.

Juste 1 ou 2 points : Que se passe-il si tu ajoutes une fonction
permettant de convertir un Image<ColorPix*> en Image<PixGray*> ?
Plutôt que de copier le tableau, pourquoi ne pas prendre une référence
dessus ? Voire même avoir un contrôle statique ne faisant rien au
runtime (boost::static_assert, avec boost::type_traits::is_same, devrait
pouvoir t'aider).

Le truc avec enable_if me semble en l'occurence beaucoup trop malin, et
en plus apparait dans la déclaration, ce qui à mon avis l'embrouille.

--
Loïc

Loïc Joly
Le #305668

template<class PixType>
class Image
{
public:
void Gradient()
{
const Image<PixGray*> compileTimeError = this;
}
};
mais c'est pas vraiment satisfaisant d'avoir la fonction dans toutes
les classes avec un truc qui compile pas si on tente de l'appeler.



Une fonction template n'est instanciée que si elle est utilisée. Donc ta
fonction ne sera que dans des classes où elle est utilisable. Et avoir
un truc qui ne compile pas me semble le meilleur niveau d'erreur qu'on
puisse obtenir.


Juste une précision : C'est déjà la méthode largement utilisée par la
STL, comme dans std::list::sort, ou dans le constructeur de vector qui
prend uniquement une taille. Ca pourra certainement s'exprimer plus
clairement quand il y aura des concepts en C++ (bientôt j'espère), mais
en attendant, je ne suis pas trop pour les astuces à base de template
qui alourdissent le code plus qu'elles ne le clarifient.

--
Loïc


Mathias Gaunard
Le #306385
template<class PixType>
class Image
{
public:
void Gradient()
{
const Image<PixGray*> compileTimeError = this;
}
};
mais c'est pas vraiment satisfaisant d'avoir la fonction dans toutes
les classes avec un truc qui compile pas si on tente de l'appeler.


Une fonction template n'est instanciée que si elle est utilisée.


Gradient n'est pas une fonction template, mais une fonction membre d'une
classe template.


Loïc Joly
Le #306363


Gradient n'est pas une fonction template, mais une fonction membre
d'une classe template.


Effectivement, problème de vocabulaire. Il n'en reste pas moins qu'il
n'y aura pas instanciation sauf si nécessaire.

14.7.1.9
An implementation shall not implicitly instantiate a function
template, a member template, a nonvirtual member function, a member
class or a static data member of a class template that does not
require instantiation.



--
Loïc

Publicité
Poster une réponse
Anonyme