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 :

Fabrique de patrons de classes


Sujet :

C++

  1. #1
    Membre éclairé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2010
    Messages
    517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Santé

    Informations forums :
    Inscription : Avril 2010
    Messages : 517
    Points : 718
    Points
    718
    Par défaut Fabrique de patrons de classes
    Bonjour tout le monde,

    Je suis en train de voir comment faire une fabrique pour des patrons de classes:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template<typename PixelType, size_t ImageDim>
    class Image
    {
    };
    Je sais que ça ne fonctionne pas mais j'aimerai un quelque chose de ce style:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    auto createImage(const char * pixelType, size_t ImageDim)
    {
       switch(ImageDim){
         case 2:
         constexpr size_t dim = 2;
         switch(pixelType){
           case "short":
                return std::make_shared<Image<short, dim>>();
          // idem pour les autres types de données possibles (char, long, unsigned char....) et dimensions possibles (1D, 2D, 3D, 4D)
         }
       }
    }
    Ce que j'avais imaginé au début, c'est de faire une classe de base dont hérite la classe Image et créer des fabriques pour chaque possibilités d'image. Seulement, je ne peux pas modifier cette classe image... Du coup, j'ai eu une autre idée:
    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
    enum PixelType
    {
      CHAR, 
      SHORT, 
      INT, 
      LONG
    };
     
    template<typename PType, size_t Dim>
    class Image
    {
    };
     
    class AbstractImage
    {
    };
     
    template<typename PType, size_t ImageDim>
    class ImageImpl : public AbstractImage, public Image<PType, ImageDim>
    {
        public:
            ImageImpl() { std::cout << "Image<" << typeid(PType).name() << ", " << ImageDim << ">" << std::endl; }
    };
     
    class CreatorImage
    {
    public:
    	virtual std::shared_ptr<AbstractImage> create() = 0;
    };
     
    template<class T>
    class CreatorImageImpl : public CreatorImage
    {
    public:
    	virtual std::shared_ptr<AbstractImage> create() { return std::make_shared<T>(); }
    };
     
    class FactoryImage
    {
    public:
    	static void registerCreator(PixelType type, size_t dim, std::shared_ptr<CreatorImage> creator)
    	{
    		creatorMap()[dim][type] = creator;
    	}
     
    	std::shared_ptr<AbstractImage> create(PixelType type, size_t dim) const
    	{
    		return creatorMap().at(dim).at(type)->create();
    	}
     
    private:
    	static std::map<size_t, std::map<PixelType, std::shared_ptr<CreatorImage>>> & creatorMap()
    	{
    	    static std::map<size_t, std::map<PixelType, std::shared_ptr<CreatorImage>>> map;
    	    return map;
    	}
    };
     
    int main()
    {
        FactoryImage::registerCreator(PixelType::CHAR, 2, std::make_shared<CreatorImageImpl<ImageImpl<char, 2>>>());
        FactoryImage factory;
        factory.create(PixelType::CHAR, 2);
    }
    Ce code fonctionne, on peut ajouter plus ou moins autant de type que l'on souhaite, mais j'aime pas trop la map de map, le fait d'enregistrer des "Creator" et surtout de devoir surcharger une classe Abstraite (qui implique plus ou moins de redéfinir les fonctions présentent dans la classe Image pour plus de commodités).

    Est-ce que quelqu'un à d'autres suggestions?

    Merci

  2. #2
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 281
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 281
    Points : 11 029
    Points
    11 029
    Par défaut
    Tu ne peux effectivement pas avoir une fonction qui renvoie des types différents.
    Pour avoir manipuler ce genr ede choses, c'est vite casse-gueule. Maintenant, nos images ont un parent générique commun.

    Ce que tu as fait est une possibilité. Tu pourrai aussi passer par du (boost:any, mais pas des variants (trop de possibilités pour le size).
    Au final, ce que tu as fait est assez proche de any dans l'idée.

  3. #3
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 490
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 490
    Points : 6 189
    Points
    6 189
    Par défaut
    Bonjour.

    Partons du modèle de classe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<typename PixelType, size_t ImageDim>
    class Image final
    {
    	// code
    };
    Admettons qu'il existe des opérations qui dépendent du type de l'image :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template<typename PixelType, size_t ImageDim>
    void foo(Image<PixelType, ImageDim> const& image) {
    	// code
    }
    Admettons que le type d'une certaine image soit inconnu à l'exécution. Par exemple, on charge une image dans un fichier et c'est le contenu du fichier qui permet de déduire le type de l'image. Une fois l'image chargée, on veut faire des opérations dessus qui dépendent de son type.

    Une approche orientée objet consiste à créer une classe de base AbstractImage. Chaque opération sur une image qui dépend du type de l'image se traduit par une fonction virtuelle dans AbstractImage :
    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
    class AbstractImage {
    public:
    	virtual ~AbstractImage() = default;
    	virtual void virtFoo() const = 0;
    protected:
    	AbstractImage()                                = default;
    	AbstractImage(AbstractImage const&)            = default;
    	AbstractImage(AbstractImage&&)                 = default;
    	AbstractImage& operator=(AbstractImage const&) = default;
    	AbstractImage& operator=(AbstractImage&&)      = default;
    };
     
    template<typename PixelType, size_t ImageDim>
    class ImageWrapper final : public AbstractImage {
    public:
    	void virtFoo() const override {
    		foo(m_image);
    	}
    private:
    	Image<PixelType, ImageDim> m_image;
    };
    On peut alors utiliser le patron de conception Fabrique. Voici comment on pourrait l'implémenter :
    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
    enum class PixelType { Char, Short, Int, Long };
     
    std::unique_ptr<AbstractImage> createImage(PixelType pixelType, size_t imageDim)
    {
    	assert(imageDim >= 1 && imageDim <= 4);
    	switch(imageDim){
    	case 1:
    		{
    			constexpr size_t dim = 1;
    			switch(pixelType){
    			case PixelType::Char:  return std::make_unique<ImageWrapper<char,  dim>>();
    			case PixelType::Short: return std::make_unique<ImageWrapper<short, dim>>();
    			case PixelType::Int:   return std::make_unique<ImageWrapper<int,   dim>>();
    			case PixelType::Long:  return std::make_unique<ImageWrapper<long,  dim>>();
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	case 2:
    		{
    			constexpr size_t dim = 2;
    			switch(pixelType){
    			case PixelType::Char:  return std::make_unique<ImageWrapper<char,  dim>>();
    			case PixelType::Short: return std::make_unique<ImageWrapper<short, dim>>();
    			case PixelType::Int:   return std::make_unique<ImageWrapper<int,   dim>>();
    			case PixelType::Long:  return std::make_unique<ImageWrapper<long,  dim>>();
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	case 3:
    		{
    			constexpr size_t dim = 3;
    			switch(pixelType){
    			case PixelType::Char:  return std::make_unique<ImageWrapper<char,  dim>>();
    			case PixelType::Short: return std::make_unique<ImageWrapper<short, dim>>();
    			case PixelType::Int:   return std::make_unique<ImageWrapper<int,   dim>>();
    			case PixelType::Long:  return std::make_unique<ImageWrapper<long,  dim>>();
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	case 4:
    		{
    			constexpr size_t dim = 4;
    			switch(pixelType){
    			case PixelType::Char:  return std::make_unique<ImageWrapper<char,  dim>>();
    			case PixelType::Short: return std::make_unique<ImageWrapper<short, dim>>();
    			case PixelType::Int:   return std::make_unique<ImageWrapper<int,   dim>>();
    			case PixelType::Long:  return std::make_unique<ImageWrapper<long,  dim>>();
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	default:
    		throw std::invalid_argument(
    			std::string{__func__} + " : invalid imageDim argument : " +
    			std::to_string(imageDim) + "."
    		);
    	}
    }
    Remarques :
    • Pour découpler davantage le code, le type de retour de ma fonction createImage est std::unique_ptr<AbstractImage> et non pas std::shared_ptr<AbstractImage>. De toute façon, std::unique_ptr est implicitement convertible en std::shared_ptr. Donc, si l'utilisateur veut std::shared_ptr, il lui suffit d'écrire :
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      std::shared_ptr<AbstractImage> image = createImage(pixelType, imageDim);
    • Mon implémentation de createImage ne respecte pas le principe DRY (Don't Repeat Yourself), car le même bout de code apparaît 4 fois. Pour factoriser cela, on pourrait faire des super bidouillages avec des variadic templates et std::index_sequence, mais le code deviendrait illisible. Je préfère attendre que C++ ait l'équivalent du static foreach du langage D, en espérant que cela arrive un jour.
    • Je déconseille d'écrire des constantes entièrement en majuscules (ex : CHAR), car cela augmente les risques de collision avec des macros.


    Une autre approche consiste à utiliser std::variant :
    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
    using ImageVariant =
    	std::variant<
    		Image<char,1>, Image<short,1>, Image<int,1>, Image<long,1>,
    		Image<char,2>, Image<short,2>, Image<int,2>, Image<long,2>,
    		Image<char,3>, Image<short,3>, Image<int,3>, Image<long,3>,
    		Image<char,4>, Image<short,4>, Image<int,4>, Image<long,4>
    	>;
     
    ImageVariant createImageVariant(PixelType pixelType, size_t imageDim)
    {
    	assert(imageDim >= 1 && imageDim <= 4);
    	switch(imageDim){
    	case 1:
    		{
    			constexpr size_t dim = 1;
    			switch(pixelType){
    			case PixelType::Char:  return Image<char,  dim>{};
    			case PixelType::Short: return Image<short, dim>{};
    			case PixelType::Int:   return Image<int,   dim>{};
    			case PixelType::Long:  return Image<long,  dim>{};
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	case 2:
    		{
    			constexpr size_t dim = 2;
    			switch(pixelType){
    			case PixelType::Char:  return Image<char,  dim>{};
    			case PixelType::Short: return Image<short, dim>{};
    			case PixelType::Int:   return Image<int,   dim>{};
    			case PixelType::Long:  return Image<long,  dim>{};
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	case 3:
    		{
    			constexpr size_t dim = 3;
    			switch(pixelType){
    			case PixelType::Char:  return Image<char,  dim>{};
    			case PixelType::Short: return Image<short, dim>{};
    			case PixelType::Int:   return Image<int,   dim>{};
    			case PixelType::Long:  return Image<long,  dim>{};
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	case 4:
    		{
    			constexpr size_t dim = 4;
    			switch(pixelType){
    			case PixelType::Char:  return Image<char,  dim>{};
    			case PixelType::Short: return Image<short, dim>{};
    			case PixelType::Int:   return Image<int,   dim>{};
    			case PixelType::Long:  return Image<long,  dim>{};
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	default:
    		throw std::invalid_argument(
    			std::string{__func__} + " : invalid imageDim argument : " +
    			std::to_string(imageDim) + "."
    		);
    	}
    }
    Exemple d'utilisation :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    ImageVariant const imageVariant = createImageVariant(pixelType, imageDim);
    std::visit(
    	[](auto& x){foo(x);},
    	imageVariant
    );
    Edit 2018-07-20-00h20 : changement du code : programmation par contrat.
    Edit 2018-07-20-02h33 : légère reformulation.

  4. #4
    Membre éclairé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2010
    Messages
    517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Santé

    Informations forums :
    Inscription : Avril 2010
    Messages : 517
    Points : 718
    Points
    718
    Par défaut
    Tout d'abord merci à vous deux.

    @Luc je me doutais qu'il n'y avait pas de nouvelles fonctionnalités dans c++ pour qu'une fonction renvoie différents types (et heureusement car ça risque de devenir compliqué à lire)

    @Pyramidev: tu m'as appris pas mal de choses notamment la conversion de std::unique_ptr vers std::shared_ptr.
    Pour ce qui est de l'enum, je suis complètement d'accord avec toi mais je ne peux pas le modifier: il appartient à la bibiliothèque ITK (ainsi que cette fameuse classe Image).
    Concernant tes deux implémentations:
    - la première était un de mes premiers essais, cependant il suffit que je ne pense pas à tous les cas de figures (pour les types ça va, mais pour les dimensions, je ne sais pas si on peut s'arrêter "seulement" à 4). Est-ce vraiment un problème? Je ne sais/pense pas. Par contre ce qui m'ennuie, c'est de devoir rappeler toutes les fonctions de Image (qui sont relativement conséquentes).
    - la seconde, je trouve l'utilisation des variants assez pratique bien que comme le dit Luc, il va y avoir beaucoup de possibilités (j'ai volontairement limité le nombre de types de pixel). De plus, comme pour la première méthode, il faut implémenter X fonctions afin d'utiliser l'ensemble des méthodes disponibles dans la classe Image.

    Je vais continuer à creuser la question mais je pense que je vais faire un mix d'un peu de toutes les idées.

    Merci pour tout!

  5. #5
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 281
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 281
    Points : 11 029
    Points
    11 029
    Par défaut
    Pas eu le temps de tout lire, mais ITK propose déjà un parent commun -- je me disais bien que ce modèle me disait quelque chose. Leur solution est de passer par des downcastings dans tous les sens. Accessoirement, ITK impose ses propres pointeurs intelligents pre-boost. Oublie unique_ptr & cie pour les images ITK.
    Je me souviens que nous avons des factories dans OTB, mais je ne sais plus à quel niveau.

    Je suis curieux, ITK t'es imposée par le boulot, ou c'est un choix personnel?

  6. #6
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut correction code
    Salut,

    @Pyramidev: Je me demande si je n'irais pas encore plus loin, car je ne sais pas si tu as remarqué, mais ton code
    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
    std::unique_ptr<AbstractImage> createImage(PixelType pixelType, size_t imageDim)
    {
    	assert(imageDim >= 1 && imageDim <= 4);
    	switch(imageDim){
    	case 1:
    		{
    			constexpr size_t dim = 1;
    			switch(pixelType){
    			case PixelType::Char:  return std::make_unique<ImageWrapper<char,  dim>>();
    			case PixelType::Short: return std::make_unique<ImageWrapper<short, dim>>();
    			case PixelType::Int:   return std::make_unique<ImageWrapper<int,   dim>>();
    			case PixelType::Long:  return std::make_unique<ImageWrapper<long,  dim>>();
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	case 2:
    		{
    			constexpr size_t dim = 2;
    			switch(pixelType){
    			case PixelType::Char:  return std::make_unique<ImageWrapper<char,  dim>>();
    			case PixelType::Short: return std::make_unique<ImageWrapper<short, dim>>();
    			case PixelType::Int:   return std::make_unique<ImageWrapper<int,   dim>>();
    			case PixelType::Long:  return std::make_unique<ImageWrapper<long,  dim>>();
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	case 3:
    		{
    			constexpr size_t dim = 3;
    			switch(pixelType){
    			case PixelType::Char:  return std::make_unique<ImageWrapper<char,  dim>>();
    			case PixelType::Short: return std::make_unique<ImageWrapper<short, dim>>();
    			case PixelType::Int:   return std::make_unique<ImageWrapper<int,   dim>>();
    			case PixelType::Long:  return std::make_unique<ImageWrapper<long,  dim>>();
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	case 4:
    		{
    			constexpr size_t dim = 4;
    			switch(pixelType){
    			case PixelType::Char:  return std::make_unique<ImageWrapper<char,  dim>>();
    			case PixelType::Short: return std::make_unique<ImageWrapper<short, dim>>();
    			case PixelType::Int:   return std::make_unique<ImageWrapper<int,   dim>>();
    			case PixelType::Long:  return std::make_unique<ImageWrapper<long,  dim>>();
    			}
    			throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    		}
    	default:
    		throw std::invalid_argument(
    			std::string{__func__} + " : invalid imageDim argument : " +
    			std::to_string(imageDim) + "."
    		);
    	}
    }
    n'est que répétition à tout va.
    De plus, ce que tu traites comme erreur d'exécution pourrait parfaitement être intégré dans le contra à la compilation
    Je commencerais donc par modifier un tout petit peu ton énumération pour lui donner la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    enum class PixelType { 
        Char, 
        Short, 
        Int,  
        Long,
        Max // parce que j'aime connaitre cette valeur, et parce qu'elle pourra me servir de valeur par défaut ;)
     };
    Je créerais ensuite un trait de politique qui associe chacune de ces valeurs énumérées à un type de donnée bien particulier (ce sera sans doute le code le plus long ) sous 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
    18
    19
    20
    21
    template <PixelType P> // sera utilisé avec PixelType::Max ;)
    struct PixelTrait{
        static_assert(false, "unknown pixel type");
        using value_type = void;
    };
    template <>
    struct PixelTrait<PixelType::Char>{
        using value_type = char;
    };
    template <>
    struct PixelTrait<PixelType::Short>{
        using value_type = short;
    };
    template <>
    struct PixelTrait<PixelType::Int>{
        using value_type = int;
    };
    template <>
    struct PixelTrait<PixelType::Long>{
        using value_type = long;
    };
    qui me permettrait d'éviter les répétitions du genre de case PixelType::XXX: return std::make_unique<ImageWrapper<SOMETYPE, dim>>();en créant une politique proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template <PixelType Type, size_t Dim>
    struct ImageCreator{
        /* des alias de types créés par facilité uniquement ;) */
        using trait_type = PixelTrait<Type>;
        using value_type = typename trait_type::value_type;
        static std::unique_ptr<AbstractImage> create(){
            return std::make_unique<ImageWrapper<value_type,  Dim>>();
        }
    };
    et qui me permettrait de modifier la fonction originale pour lui donner 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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    template <PixelType Type=PixelType>
    std::unique_ptr<AbstractImage> createImage(size_t imageDim)
    {
    	assert(imageDim >= 1 && imageDim <= 4);
    	switch(imageDim){
    	case 1:
    		{
    			constexpr size_t dim = 1;
                            return ImageCreator<Type, dim>::create();
    		}
    	case 2:
    		{
    			constexpr size_t dim = 2;
                            return ImageCreator<Type, dim>::create();
    		}
    	case 3:
    		{
    			constexpr size_t dim = 3;
                            return ImageCreator<Type, dim>::create();
    		}
    	case 4:
    		{
    			constexpr size_t dim = 4;
                            return ImageCreator<Type, dim>::create();
    		}
    	default:
    		throw std::invalid_argument(
    			std::string{__func__} + " : invalid imageDim argument : " +
    			std::to_string(imageDim) + "."
    		);
    	}
    }
    Mais, comme tu peux t'en rendre compte, les différents case ne diffèrent désormais que par la valeur associée à dim, alors que default aurait avantage à être intégré au contrat et pourrait lui aussi être vérifié à la compilation.

    Il suffirait en effet de créer une autre politique qui pourrait prendre la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template <PixelType Type, size_t Dim>
    struct ImageSelector{
        static std::unique_ptr<AbstractImage> select(){
            return ImageCreator<Type, dim>::create();
        }
    };
    /* et pour la vérification à la compilation */
    template <PixelType Type>
    struct ImageSelector<Type, 0{
        static std::unique_ptr<AbstractImage> select(){
            static_assert(false, "invalid image dim");
        }
    };
    qui nous permettrait de modifier une fois encore notre fonction "originale" pour lui donner une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <PixelType Type=PixelType::Max, size_t Dim=0>
    std::unique_ptr<AbstractImage> createImage(){
        return ImageSelector<Type, Dim>::select();
    }
    PS: En écrivant ce code, je me rend compte que l'on pourrait aller encore plus loin, vu que nous n'avons qu'un nombre clairement défini de dimensions possibles, en créant une énumération pour ces dimension ainsi que la série de trait de politiques qui va avec. Mais ca, c'est une autre histoire

  7. #7
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 490
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 490
    Points : 6 189
    Points
    6 189
    Par défaut
    koala01, tu oublies que la fonction std::unique_ptr<AbstractImage> createImage(PixelType pixelType, size_t imageDim) a été conçue exprès pour que les deux arguments puissent avoir des valeurs connues à l'exécution, mais inconnues à la compilation :
    « Admettons que le type d'une certaine image soit inconnu à l'exécution. Par exemple, on charge une image dans un fichier et c'est le contenu du fichier qui permet de déduire le type de l'image. Une fois l'image chargée, on veut faire des opérations dessus qui dépendent de son type. »

    En outre, je crains que ton code ne soit trop lourd à analyser quand on a besoin de modifier rapidement le code. Mais +1 quand même car, après avoir lu ton message, j'ai pris conscience que l'on pouvait facilement réduire une des répétitions dans mon code.

    Du coup, voici une amélioration de mon implémentation de createImage qui utilise une sous-fonction createImageWithKnownDim :
    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
    enum class PixelType { Char, Short, Int, Long };
     
    template<size_t ImageDim>
    std::unique_ptr<AbstractImage> createImageWithKnownDim(PixelType pixelType)
    {
    	switch(pixelType){
    		case PixelType::Char:  return std::make_unique<ImageWrapper<char,  ImageDim>>();
    		case PixelType::Short: return std::make_unique<ImageWrapper<short, ImageDim>>();
    		case PixelType::Int:   return std::make_unique<ImageWrapper<int,   ImageDim>>();
    		case PixelType::Long:  return std::make_unique<ImageWrapper<long,  ImageDim>>();
    	}
    	throw std::logic_error(std::string{__func__} + " : forgotten pixel type.");
    }
     
    std::unique_ptr<AbstractImage> createImage(PixelType pixelType, size_t imageDim)
    {
    	assert(imageDim >= 1 && imageDim <= 4);
    	switch(imageDim){
    		case 1: return createImageWithKnownDim<1>(pixelType);
    		case 2: return createImageWithKnownDim<2>(pixelType);
    		case 3: return createImageWithKnownDim<3>(pixelType);
    		case 4: return createImageWithKnownDim<4>(pixelType);
    	}
    	throw std::invalid_argument(
    		std::string{__func__} + " : invalid imageDim argument : " +
    		std::to_string(imageDim) + "."
    	);
    }
    Dans la version actuelle du C++, je pense que je n'aurais pas codé quelque chose de plus compliqué. Je crois que privilégier la simplicité permettra de réusiner plus facilement le code dans quelque années, dans une future époque où ceux qui amélioreront le code pourront utiliser des fonctionnalités du C++ qui ne sont pas encore disponibles aujourd'hui.

    Je me dis que, dans une future version du C++, on aura peut-être un for constexpr qui permettra de factoriser le code de createImage sous 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
    // PSEUDO-CODE. Ce n'est pas du C++ standard.
     
    std::unique_ptr<AbstractImage> createImage(PixelType pixelType, size_t imageDim)
    {
    	assert(imageDim >= 1 && imageDim <= 4);
    	for constexpr(size_t ImageDim : { 1, 2, 3, 4 }) // Chaque ImageDim est connu à la compilation.
    		if(imageDim == ImageDim)
    			return createImageWithKnownDim<ImageDim>(pixelType);
    	throw std::invalid_argument(
    		std::string{__func__} + " : invalid imageDim argument : " +
    		std::to_string(imageDim) + "."
    	);
    }
    que le compilateur transformera en :
    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
    std::unique_ptr<AbstractImage> createImage(PixelType pixelType, size_t imageDim)
    {
    	assert(imageDim >= 1 && imageDim <= 4);
    	if(imageDim == 1)
    		return createImageWithKnownDim<1>(pixelType);
    	if(imageDim == 2)
    		return createImageWithKnownDim<2>(pixelType);
    	if(imageDim == 3)
    		return createImageWithKnownDim<3>(pixelType);
    	if(imageDim == 4)
    		return createImageWithKnownDim<4>(pixelType);
    	throw std::invalid_argument(
    		std::string{__func__} + " : invalid imageDim argument : " +
    		std::to_string(imageDim) + "."
    	);
    }
    Dans la version actuelle du C++, on peut faire ce genre de factorisation avec des variadic templates et std::index_sequence, mais le code deviendrait illisible.

    Remarque : le for constexpr que j'évoque s'inspire du static foreach du langage D. Depuis C++17, on a if constexpr qui ressemble un peu au static if du langage D. Donc je me dis que for constexpr pourrait apparaître plus tard.

    Citation Envoyé par koala01 Voir le message
    Je créerais ensuite un trait de politique qui associe chacune de ces valeurs énumérées à un type de donnée bien particulier (ce sera sans doute le code le plus long ) sous 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
    18
    19
    20
    21
    template <PixelType P> // sera utilisé avec PixelType::Max ;)
    struct PixelTrait{
        static_assert(false, "unknown pixel type");
        using value_type = void;
    };
    template <>
    struct PixelTrait<PixelType::Char>{
        using value_type = char;
    };
    template <>
    struct PixelTrait<PixelType::Short>{
        using value_type = short;
    };
    template <>
    struct PixelTrait<PixelType::Int>{
        using value_type = int;
    };
    template <>
    struct PixelTrait<PixelType::Long>{
        using value_type = long;
    };
    qui me permettrait d'éviter les répétitions du genre de case PixelType::XXX: return std::make_unique<ImageWrapper<SOMETYPE, dim>>();
    Avec ton PixelTrait, peut-être qu'une future version du C++ permettra de factoriser le code de createImageWithKnownDim ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // PSEUDO-CODE. Ce n'est pas du C++ standard.
     
    template<size_t ImageDim>
    std::unique_ptr<AbstractImage> createImageWithKnownDim(PixelType pixelType)
    {
    	for constexpr(PixelType PixelTypeValue : allValuesOfEnumType<PixelType>)
    		if(pixelType == PixelTypeValue)
    			return std::make_unique<ImageWrapper<PixelTrait<PixelTypeValue>::value_type, ImageDim>>();
    	throw std::logic_error(std::string{__func__} + " : unreachable code.");
    }
    Mais, en attendant qu'on puisse faire ce genre de chose, je n'aurais pas utilisé ton PixelTrait pour factoriser le code de createImageWithKnownDim.

  8. #8
    Membre éclairé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2010
    Messages
    517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Santé

    Informations forums :
    Inscription : Avril 2010
    Messages : 517
    Points : 718
    Points
    718
    Par défaut
    Excusez-moi pour cet absence de réponse.

    @Luc Hermite: C'est entre le choix personnel et imposé par le boulot: On bosse déjà avec VTK et donc je me suis dis que l'intégration ITK-VTK serait plus simple que d'utiliser OpenCV par exemple ou encore de coder ma propre bibliothèque d'analyse d'images. Aurais-tu des conseils à me donner? J'ai vu tous les down-cast à faire c'est pour ça que je voulais éviter le plus possible à devoir en faire et avoir une fabrique "templatisée" pour ce genre de chose. Après je ferais globalement la même chose pour les filtres.

    @koala01: Le trait factorise pas mal mais effectivement ce code fonctionne lors de la compilation mais pas à l'exécution.

    @Pyramidev: Très bonne idée le for constexpr. Peut-être pour C++75 ^^.

  9. #9
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 281
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 281
    Points : 11 029
    Points
    11 029
    Par défaut
    Je ne 'ai pas solution propre à te donner dans l'immédiat. Quitte à utiliser ITK, vise les dernières versions (5 -- en béta? je ne sais plus) qui améliore la gestion du multithréading et diverses autres choses.
    Sinon, tu es prisonnier des pointeurs intelligents d'ITK pour les images (attention, il sont plein de pièges), et je te dirai bien de faire à Rome comme à Rome: avec des dynamic_cast

Discussions similaires

  1. [POO] création d'objet via une fabrique de classe
    Par flash_math dans le forum Langage
    Réponses: 2
    Dernier message: 10/11/2007, 09h15
  2. patron de classe
    Par Azharis dans le forum C++
    Réponses: 2
    Dernier message: 14/12/2005, 07h17
  3. Dériver un patron de classe à partir du patron ?
    Par Serge Iovleff dans le forum C++
    Réponses: 4
    Dernier message: 07/01/2005, 15h31

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