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 :

choix d'une fonction en argument : lambda, class ou structure ? #optimisation


Sujet :

C++

  1. #1
    Membre éclairé Avatar de Matthieu76
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Mars 2013
    Messages
    568
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2013
    Messages : 568
    Points : 890
    Points
    890
    Par défaut choix d'une fonction en argument : lambda, class ou structure ? #optimisation
    Bonjour, je bosse sur de l'algorithmie et je suis confronter à un problème de conception et d’optimisation.

    Comment faire pour choisir une fonction en paramètre de manière efficace ?
    En gros je veux un truc du genre :

    L'utilisateur de la lib définie :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Algo *test = new algo(cosinus); // le cosinus provenant d'une enum
    Et dans le constructeur de Algo, un truc du genre:
    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 functionEnum
    {
        cosinus,
        sinus,
        tangent
    };
     
    class Algo
    {
        public :
     
        Algo(functionEnum e)
        {
             if(e == cosinus)
                 func = [](float x) { return cos(x); };
     
             else if(e == sinus)
                 func = [](float x) { return sin(x); };
     
             else if(e == tangent)
                func = [](float x) { return tan(x); };
         }
     
        private :
     
        float func;
    }
    Cela fonctionne très bien mais j'aimerais externaliser mes fonctions dans un autre ficher pour des raison de lisibilité. Dois-je faire une class static avec pour stocker mes fonctions ? Du coup, plus besoin de lambda ? Ou alors est-il plus propre de faire une class dérivé d'Algo pour chaque fonction ? Sinon je peux faire une structure contenant un pointer vers la fonction mais cela est trop car en réalité il s'agit de réseaux de neurones et je ne peux pas créer un pointer vers une fonction par neurones, cela serait trop lourd et ferait trop de répétission, non ? D'ailleurs, les lambda/pointers de fonction sont-ils stocker dans le tas ou la pile ?

    Bien évidemment cela doit rester le plus optimiser possible car les fonctions sont appelé plusieurs 100ène de milliers de fois pas seconde.


    PS : Les cos, sin et tan ne sont ici que pour l'exemple, mes fonctions sont en réalité un peu plus complexes et plus nombreuses que cela et c'est aussi pour cela que je ne peux pas directement mettre mes fonctions dans ma classe Algo.
    D'ailleurs il ne s'agit pas juste d'une fonction mais d'un couple de fonction : la fonction et sa dérivée associé.

    Merci d'avance pour votre aide

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 629
    Points : 10 554
    Points
    10 554
    Par défaut
    Et un truc comme cela, mais niveau performances je ne sais pas si c'est bon

    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
    #include <iostream>
     
     
    typedef enum e_func_name {
        func_name_cosinus = 0,
        func_name_other,
        func_name_sinus,
        func_name_tangent,
        func_name_unknown,
        NB_FUNC_NAMES
    } FUNC_NAME;
     
     
    template<FUNC_NAME func_name>
    class Algo
    {
    public:
     
        void operator() () {
            std::cout << "Algo : default" << std::endl;
        }
    };
     
     
    template<>
    class Algo<func_name_cosinus>
    {
    public:
     
        void operator() () {
            std::cout << "Algo : cosinus" << std::endl;
        }
    };
     
     
    template<>
    class Algo<func_name_sinus>
    {
    public:
     
        void operator() () {
            std::cout << "Algo : sinus" << std::endl;
        }
    };
     
     
    template<>
    class Algo<func_name_tangent>
    {
    public:
     
        void operator() () {
            std::cout << "Algo : tangent" << std::endl;
        }
    };
     
     
    class Execute_Algo
    {
    public:
     
        template<FUNC_NAME func_name>
        inline void run() {
            Algo<func_name> algo;
     
            algo();
        }
     
     
        inline void run(unsigned short name) {
            switch(name) {
            case func_name_cosinus: run<func_name_cosinus>(); break;
            case func_name_sinus:   run<func_name_sinus>();   break;
            case func_name_tangent: run<func_name_tangent>(); break;
            default: run<func_name_other>(); break;
            }
        }
    };
     
     
    int main()
    {
        Execute_Algo exec;
     
        std::cout << "**** Static ****" << std::endl;
     
        exec.run<func_name_cosinus>();
        exec.run<func_name_other>();
        exec.run<func_name_sinus>();
        exec.run<func_name_tangent>();
        exec.run<func_name_unknown>();
     
        std::cout << std::endl << "**** Inside a loop ****" << std::endl;
     
        for (unsigned short name; name < NB_FUNC_NAMES; ++name) {
            exec.run(name);
        }
     
        return 0;
    }

  3. #3
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Ce que tu souhaites faire ressembler bigrement au design pattern "stratégie" : https://www.tutorialspoint.com/desig...gy_pattern.htm

    Dans ton cas précis (mais qui est peut-être trop limitant), pourquoi ne pas se contenter d'un pointeur sur fonction ?

  4. #4
    Membre éclairé Avatar de Matthieu76
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Mars 2013
    Messages
    568
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2013
    Messages : 568
    Points : 890
    Points
    890
    Par défaut
    Citation Envoyé par foetus Voir le message
    Et un truc comme cela, mais niveau performances je ne sais pas si c'est bon
    Merci de ta réponse.
    Ta solution paraît un peu complexe et ce qui me dérange beaucoup c'est le switch la fonction run.

    J'y ai réfléchie et es-ce que ça serait une bonne solution ?
    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
    Class Aglo
    {
        public :
     
        Algo() 
        {
            switch(name)
            {
                case func_name_cosinus: function = FuncList::cosinus(x); break;
                // etc.
            }
        }
     
       private:
     
       float function;
    }
     
    Class FuncList
    {
        public:
     
        static float cosinus = [](float x)->float
        { 
            return std::cos(x); 
        };
        // etc.
    }
    Faut encore que je vois si je dois utiliser des lambdas ou des pointers de fonction, mais en gros, j'ai une list de fonction dans une classe static et dans le constructeur de Algo je fais pointer ma variable function vers la fonction que je souhaite utiliser.

    Es-ce une bonne manière de faire ?

    Puis-je faire pointer une variable vers un lambda ou dois-je utiliser des pointers de fonction ?

    Citation Envoyé par Bktero Voir le message
    Ce que tu souhaites faire ressembler bigrement au design pattern "stratégie"
    Oui, c'est à peu près ça mais il est impossible de faire des interfaces en C++ ?

    Citation Envoyé par Bktero Voir le message
    Dans ton cas précis (mais qui est peut-être trop limitant), pourquoi ne pas se contenter d'un pointeur sur fonction ?
    Effectivement je pense qu'il s'agit de la bonne solution mais je n'ai jamais utiliser des pointeurs sur fonction.
    Je fais faire une classe static regroupant des templates contenant 2 pointeurs sur fonction, la fonction ainsi que sa dérivée.
    et ensuite je choisirais le template dans le constructeur.

  5. #5
    Membre émérite
    Avatar de prgasp77
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Juin 2004
    Messages
    1 306
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Eure (Haute Normandie)

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Juin 2004
    Messages : 1 306
    Points : 2 466
    Points
    2 466
    Par défaut
    Citation Envoyé par Matthieu76 Voir le message
    Bonjour, je bosse sur de l'algorithmie et je suis confronter à un problème de conception et d’optimisation.
    Je veux bien entendre que vous êtes confronté à un problème de conception, mais j'ai des doutes sur l'optimisation. L'optimisation à priori est la racine de tous les maux.

    Vous avez besoin de créer un objet dont le comportement diffère en fonction d'une donnée d'entrée (le type de fonction sous-jacente). Son comportement diffère en utilisant en interne des fonctions externes (?) pour traiter les données qui lui seront fournies durant son utilisation. Je suppose que le traitement de ces données est gourmand en ressources, tant en terme de mémoire que de calcul. À côté de ça, une unique indirection est complètement négligeable.

    Dans un premier temps, concentrez-vous sur ces éléments de conception pour écrire un code facile à lire, à comprendre, à maintenir, etc. Une fois cela fait, si vous rencontrez des soucis de performance, vous pourrez profiler votre application pour détecter où du temps/de la mémoire est inutilement perdu.

    Plusieurs angles d'attaque s'offrent à vous :
    1. Laisser l'utilisateur de la classe Algo fournir les fonctions sous-jacentes ;
    2. Déduire les fonctions sous-jacentes en interne en fonction d'un indice d'entrée (comme un flag).


    Pour l'option #1 :
    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
    #include <iostream>
    #include <cmath>
     
     
    // Definition
    class Algo
    {
    public:
        using function_type = double(*)(double); // une fonction prenant un double et retournant un double (par exemple)
    private:
        function_type _function;
        function_type _derivative;
    public:
        Algo(function_type function, function_type derivative) : _function(function), _derivative(derivative) {}
        double do_some_work(double value) const { return 3.0 * _derivative(value) / _function(value); }
    }; // nota: Algo pourrait être templetisé selon le type de fonction sous-jacente ; cela améliorerait même sa lisibilité
     
     
    int main()
    {
        // Usage
        auto algo_sin = Algo{std::sin, std::cos};
        std::cout << algo_sin.do_some_work(22.0/7) << "\n";
    }
    Pour l'option #2 :
    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
    #include <iostream>
    #include <cmath>
    #include <tuple>
     
    // Definition
    class Algo
    {
    public:
        enum algo_type { sinus, /*...*/ };
    private:
        using function_type = double(*)(double); // par exemple
        static function_type function(algo_type type)   { return std::get<0>(function_and_derivative(type)); }
        static function_type derivative(algo_type type) { return std::get<1>(function_and_derivative(type)); }
        static std::tuple<function_type, function_type> function_and_derivative(algo_type type)
        {
            switch (type)
            {
            case sinus: return std::make_tuple<function_type, function_type>(std::sin, std::cos);
            // ...
            }
            return std::make_tuple(nullptr, nullptr);
        }
     
        function_type _function;
        function_type _derivative;
    public:
        Algo(algo_type type) : _function(function(type)), _derivative(derivative(type)) {}
        double do_some_work(double value) const { return 3.0 * _derivative(value) / _function(value); }
    };
     
     
    int main()
    {
        // Usage
        auto algo_sin = Algo{Algo::sinus};
        std::cout << algo_sin.do_some_work(22.0/7) << "\n";
    }
    Il s'agit d'implémentations minimales qui sont très restrictives et ne fonctionnent que si les fonctions sous-jacentes à utiliser sont définies comme fonctions au sens de C++. Dans le cas où il est nécessaire de composer des fonctions sans devoir les définir (par exemple pour pouvoir définir à la volée -sin à partir de sin), il sera nécessaire d'ajouter une couche d'indirection, comme par exemple définir Algo::_* comme std::function<double(double)> par exemple. Cela permettrait d'écrire quelque chose comme auto algo_sin = Algo{std::cos, [](double v) { return -std::sin(v); }};. (live demo)

    Les différentes options on des avantages et inconvénients qu'il vous faudra étudier. Dans le cas de #1, l'utilisateur de la classe peut définir les fonctions qu'il veut, rendant la classe plus versatile et universelle. Dans le cas #2, la classe a plus de contrôle sur l'usage qu'il en est fait, seules des fonctions prédéfinies sont utilisables. Mais dans tous les cas, les fonctions sous-jacentes peuvent être définies dans un autre fichier source, voire dans un autre binaire. Quant à l'usage de std::function, si l'appel à operateur() à chaque traitement de données a un impact mesuré comme négatif sur les performances de votre application, il vous est possible et mettre en cache l'indirection pour qu'elle n'ait lieu qu'une unique fois. Mais il s'agit là d'un autre problème.
    -- Yankel Scialom

  6. #6
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    J'aurais tendance à faire simple
    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
    #include <array>
     
    typedef int (*my_2_int_operand)(int, int);
     
    int myadd(int a, int b)
    	{ return a + b; }
     
    int mysub(int a, int b)
    	{ return a - b; }
     
    int mymul(int a, int b)
    	{ return a * b; }
     
    constexpr std::array<my_2_int_operand, 3> my_2_int_funcs = { myadd, mysub, mymul };
     
    enum my_2_int_enum
    { myadd_e, mysub_e, mymul_e };
     
     
    typedef float (*my_1_float_operand)(float);
     
    float mysin(float f)
    	{ return sin(f); }
     
    float mycos(float f)
    	{ return cos(f); }
     
    float mytan(float f)
    	{ return tan(f); }
     
    constexpr std::array<my_1_float_operand, 3> my_1_float_funcs = { mysin, mycos, mytan };
     
    enum my_1_float_enum
    { mysin_e, mycos_e, mytan_e };
     
     
    int run_2_int_algo(int a, int b, my_2_int_enum e)
    	{ return my_2_int_funcs[e](a, b); }
     
    float run_1_float_algo(float f, my_1_float_enum e)
    	{ return my_1_float_funcs[e](f); }
     
     
    int main()
    {
    	int i = run_2_int_algo(5, 12, myadd_e);
    	float f = run_1_float_algo(3.14159f, mysin_e);
    	return 0;
    }
    Les fonctions dans un .cpp, les enum et la définition des 2 fonctions run_algo dans un .h, et ta logique d'appel ailleurs dans ton code.
    C'est à la fois lisible, simple et performant, un tableau prédéfini à la compilation étant plus rapide qu'un switch au runtime.
    En supposant que j'aie compris la demande

  7. #7
    Membre éclairé Avatar de Matthieu76
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Mars 2013
    Messages
    568
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2013
    Messages : 568
    Points : 890
    Points
    890
    Par défaut
    Citation Envoyé par prgasp77 Voir le message
    Je suppose que le traitement de ces données est gourmand en ressources, tant en terme de mémoire que de calcul. À côté de ça, une unique indirection est complètement négligeable.
    Bah en fait je bosse sur des réseaux de neurones et ma fonction est appeler environ 125 millions de fois par seconde. Donc oui, il s'agit bien d'un problème d'optimisation ! Utilise un switch ou un if dans ma fonction est inconcevable c'est pour cela que je passe par des pointeurs de fonction. C'est ça ou alors faire une classe enfant pour chaque couple fonction/dérivé.

    Citation Envoyé par prgasp77 Voir le message
    1. Laisser l'utilisateur de la classe Algo fournir les fonctions sous-jacentes ;
    2. Déduire les fonctions sous-jacentes en interne en fonction d'un indice d'entrée (comme un flag).
    En gros je voudrais utiliser l'impémentation de la solution 1 mais ou l'utilisateur passe un enum en arguments du constructeur et dans celui-ci le pointeur sur fonction et défie par rapport à l'enum. (Je sais pas si je suis très clair)


    Citation Envoyé par camboui Voir le message
    Les fonctions dans un .cpp, les enum et la définition des 2 fonctions run_algo dans un .h, et ta logique d'appel ailleurs dans ton code.
    Oui c'est exactement ça que je veux merci.

    Ça me donnera donc un truc du genre :

    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
    typedef float (*myFunc)(int, int);
     
    int myAdd(int a, int b)
    	{ return a + b; }
     
    int mySub(int a, int b)
    	{ return a - b; }
     
    int myMul(int a, int b)
    	{ return a * b; }
     
    enum myFuncEnum
    { add, sub, mul };
     
    Class Algo
    {
        public :
     
        Algo(enum e, a, b)
        {
            this.a = a;
            this.b = b;
     
            if(e == add)
                algoFunc = myAdd;
     
            // etc.    
        }
     
        void run()
        {
            for(int i = 0; i < 10000; i++)
                algoFunc(a, b);
        }
     
        private :
            myFunc algoFunc;
            int a;
            int b;
     
    int main()
    {
            Algo test(10, 5, add);
            test.run();
    	return 0;
    }
    Avec surement quelques erreurs de syntaxe bien entendu

  8. #8
    Membre émérite
    Avatar de prgasp77
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Juin 2004
    Messages
    1 306
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Eure (Haute Normandie)

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Juin 2004
    Messages : 1 306
    Points : 2 466
    Points
    2 466
    Par défaut
    Citation Envoyé par Matthieu76 Voir le message
    Bah en fait je bosse sur des réseaux de neurones et ma fonction est appeler environ 125 millions de fois par seconde. Donc oui, il s'agit bien d'un problème d'optimisation !
    Mettons-nous d'accord sur les termes. Pour optimiser, il faut une base de comparaison. On optimise pas à partir de rien. Si la définition au début du XXème siècle d'optimiser était faire au mieux, son sens actuel et notamment en informatique est :
    Optimiser
    Rendre optimal, atteindre un optimum de production, obtenir le meilleur, selon un ensemble de critères, d'une chose ou d'une situation.
    Il est donc question de modification, d'amélioration. Vous ne pouvez pas dès la conception optimiser, il vous faut un premier jet qui aura besoin, ou non, d'optimisation. Ce que j'essaie d'exprimer plus ou moins efficacement, c'est que vous devez vous soucier des contraintes temporelles de votre implémentation, mais vous n'êtes pas encore en phase d'optimisation. L'optimisation à priori est la racine de tous les maux (même à 125 MTPS).

    Citation Envoyé par Matthieu76 Voir le message
    [J]e voudrais utiliser l'impémentation de la solution 1 mais ou l'utilisateur passe un enum en arguments du constructeur et dans celui-ci le pointeur sur fonction et défie par rapport à l'enum.
    C'est exactement ce que je décris dans ma seconde implémentation.

    Bref, je vous invite à relire mon message ; il semble que vous soyez passé à côté de plein d'éléments importants. La question de l'obtention de pointeur de fonction à partir de types variés (std::function, lambda, fonctions libres...) y est par exemple traité. Et nulle part il est question d' "[utiliser] un switch ou un if dans [votre] fonction".
    -- Yankel Scialom

  9. #9
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Un switch étant un simple jump, je vois pas de quoi tu as peur d'en utiliser ? Surtout à priori.
    Si tu veux vraiment des perfs, tu ne mets aucune indirection, et ta fonction serait un paramètre template de la classe.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  10. #10
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 069
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 069
    Points : 12 113
    Points
    12 113
    Par défaut
    Un switch étant un simple jump
    Heu, non, c'est au minimum une comparaison + un jump, et surtout un méga bordel dans les mécanismes de prédiction de branches, le cache des instructions, des données, etc...
    Tout ça pour voir que le seul moyen d'avoir des perf. convenables, c'est de faire ça dans le GPU donc avec rien de comparable à la solution "full CPU".

  11. #11
    Membre émérite
    Avatar de prgasp77
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Juin 2004
    Messages
    1 306
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Eure (Haute Normandie)

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Juin 2004
    Messages : 1 306
    Points : 2 466
    Points
    2 466
    Par défaut
    Citation Envoyé par bacelar Voir le message
    un méga bordel dans les mécanismes de prédiction de branches, le cache des instructions, des données, etc...
    Une expérience personnelle à partager ? On pourrait tous en profiter j'en suis sûr.
    -- Yankel Scialom

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Salut,

    Moi, d'un point de vue purement conceptuel, ce qui m'inquiète le plus, c'est d'avoir un switch ... case dans une (n'importe quelle! ) fonction.

    Car, de manière générale, c'est une pratique qui ne respecte pas l'OCP (Open Closed Principle, ou principe "ouvert fermé").

    Alors, je sais: on pourrait faire valoir que "des fonctions de trigonométrie, y en a pas des masses", que "le nombre de fonctions de trigonométrie est clairement établi" (sous entendu "il y a peu de chances que l'on vienne à devoir en rajouter une par la suite"), ou encore que "le but de cette classe est de respecter le DRY, en n'ayant du code à modifier qu'à un seul endroit en cas de besoin" et qu'il n'y a donc -- a priori -- aucune raison que l'on doive aller changer le code pour pouvoir ajouter une nouvelle fonctionnalité. Et je serai bien obligé d'être d'accord face à cette objection...

    Seulement, j'ai un problème avec le nom de la classe utilisé lors de la question initiale : Algo (pour "algorithme", d'ailleurs pourquoi élider le nom ). Car le nom d'une classe indique l'utilisation que l'on peut en faire. Or, "algorithme", c'est comme "manager" ou "gestionnaire": ce sont des termes particulièrement vagues, qui nous incitent très facilement à prendre de mauvaises décisions.

    Ainsi, si un jour, tu en viens à vouloir ajouter la fonctionnalité de calcul de la racine carrée (pour respecter le prototype de la fonction), tu as de grandes chances d'en arriver à te dire que "ben, finalement, ma classe Algo est idéal pour remplir ce besoin", et de décider d'ajouter cette fonctionnalité à ta classe Algo, dont tu avais au départ considéré qu'elle n'exécuterait ... que des fonctions de trigonométrie.

    Et, bien sur, cela t'obligeras à ... aller modifier du code qui avait pourtant été validé, ce qui est en complet désaccord avec l'OCP.

    Cela peut donc parraitre idiot, mais j'aurais sans doute moins d'objection si cette classe s'appelait Trigono (ou Trigo), car, au moins, cela éviterait le risque de prendre de mauvaises décisions

    Enfin, le service rendu par ta classe consiste s'emble-t-il -- quoi qu'il arrive -- à faire appel à des fonctions existantes, et l'utilisation d'un callback semble donc parfaitement justifiée, l'idéal étant sans doute de fournir, tout simplement, la fonction qui devra être appelée au constructeur de ton instance, 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
    class Algo{
    public:
        /* j'utilise les fonctionnalités apparues en C++11... je ne m'inquiète pas des performances */
       using callback_t = std::function<double (double)>
       Algo(callback_t callee):callee_{callee}{
       }
       double callMe(double value) const{
           return callee_(value);
       }
    private:
       callback_t callee_;
    };
    (code non testé)
    De cette manière, tu pourrais envisager d'écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    /* converti des degrés en radians ;) */
    double degToRad(double);
    int main(){
        Algo sinus(std::sin);
       /* OU OU OU */
       Algo specificThing([](double d) {return /* ... */ };);
       /* ... */
       auto result1 = sinus.callMe( degToRad(35.11));
       auto result2 = specificThing(3.1415926);
       /*...*/
    }
    Note d'ailleurs que la classe Algo pourrait tout aussi bien prendre une forme template, histoire d'accepter "n'importe quel type numérique"
    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

  13. #13
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 069
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 069
    Points : 12 113
    Points
    12 113
    Par défaut
    Alors, je sais: on pourrait faire valoir que "des fonctions de trigonométrie, y en a pas des masses"
    Je trouve qu'il y a déjà la dose juste dans la norme en terme de fonctions numérique:
    http://fr.cppreference.com/w/cpp/numeric/math

    C'est pas une application de géométrie mais une simulation de neurones, les fonctions de modélisation de ces machins sont pléthores.

  14. #14
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par Matthieu76 Voir le message
    Oui, c'est à peu près ça mais il est impossible de faire des interfaces en C++ ?
    C'est évidemment possible

    Ce n'est pas exactement une interface au sens Java mais c'est très proche. Il s'agit d'une classe ne définissant que des fonctions membres virtuelles pures. Exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class Algorithm
    {
    public:
        virtual T compute(U u, V v) = 0;
    };
    Et voilà !

    PS : comme toute classe de base, elle devrait avoir un destructeur virtuel (ne faisant rien ici).

  15. #15
    Membre éclairé Avatar de Matthieu76
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Mars 2013
    Messages
    568
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2013
    Messages : 568
    Points : 890
    Points
    890
    Par défaut
    Citation Envoyé par prgasp77 Voir le message
    Ce que j'essaie d'exprimer plus ou moins efficacement, c'est que vous devez vous soucier des contraintes temporelles de votre implémentation, mais vous n'êtes pas encore en phase d'optimisation.
    Pour l'instant je change le contenue de ma fonction directement dans le code puis je recompile. Je cherche une solution qui ne me coûterais pas plus que l'appel d'une fonction donc oui on peut dire que je suis en phase d’optimisation.

    Et puis je trouve ça débile de ne pas réfléchir à l’optimisation dès le début !

    J'ai des bouts de code où je me suis dis "on verra plus tard pour l'optimisation" et l'optimisation m'a fait changer toute l'architecture de mon programme ce qui m'a fait perdre beaucoup de temps alors que si j'avais réfléchie à l'optimisation au début j'aurais pû éviter de rechanger une partie de mon code.



    Citation Envoyé par koala01 Voir le message
    Moi, d'un point de vue purement conceptuel, ce qui m'inquiète le plus, c'est d'avoir un switch ... case dans une (n'importe quelle! ) fonction.
    Je ne vois pas en quoi cela gène d'avoir juste un switch dans le constructeur de ma classe pour changer des trucs en fonction de mon paramètre d'entrée.

    Citation Envoyé par koala01 Voir le message
    cela t'obligeras à ... aller modifier du code qui avait pourtant été validé, ce qui est en complet désaccord avec l'OCP.
    Roh ... ça va c'est juste un cas à rajouter dans le switch quand j'ajoute une fonction c'est pas la mort ! Et puis si on devait vraiment être en parfaite accord avec tout les principes de code on développerait plus rien ... Je vais pas faire un code 10 fois plus compliquer et moins clair et optimiser juste pour ne pas avoir à rajouter 1 ligne dans mon constructeur quand j’ajouterais une fonction tous les 36 du mois.

    Citation Envoyé par koala01 Voir le message
    des fonctions de trigonométrie, y en a pas des masses
    Citation Envoyé par koala01 Voir le message
    j'ai un problème avec le nom de la classe utilisé
    Nan mais réfléchie 2 secondes, ma classe ne s'appelle pas réellement Algo et n'implémente pas des fonction trigonométriques, mon code est plus complexe que ça. J'ai fait ça pour être plus clair et puis si j'avais expliqué en détail mon code, tout le mode aurait fait des remarques sur le reste de mon code et personne n'aurait répondu à ma question exactement comme tu viens de le faire en critiquant le nom de ma classe. (Je dis ça par expérience )

    En réalité dans mon code j'ai une classe NeuralNetwork contenant un vecteur de Perceptron aussi appeler neurones et c'est la fonction d'activation ainsi que la dérivée de fonction d'activation de mes perceptrons que j'aimerais choisir, avec une enum, à la création de mon réseaux de neurones. Il est vrai que j'aurais peut-être dû commencer par là. Et c'est pour cela que les pointeurs sur fonction s'y prête bien, comme ça tous mes neurones pointent vers la même fonction d'activation. Enfin j'aurais une fonction d'activation différente par couche mais ça c'est une autre histoire ...



    Citation Envoyé par Bktero Voir le message
    Il s'agit d'une classe ne définissant que des fonctions membres virtuelles pures.
    Merci j'avais pas pensé à ça, j'avais oublier le mot clé virtual. Du coup est-il possible de faire des méthodes qui prenne en argument une classe "interface" et de passer un enfant en argument ... Euh ... Bah oui évidemment je suis bête !
    Même si dans mon cas cela ne m'aide pas beaucoup car ça serait trop lourd de refaire une classe pour chaque fonction alors que tout le reste de ma classe ne change pas d'un poil.



    Bon, j'ai pris ma décision, je vais faire une classe abstraite contenant des pointeurs sur fonction et je vais faire un gros switch dans le constructeur de ma classe algo pour alloué la bonne fonction à la création de mon objet.

    Ah et si quelqu'un pourrait m'expliquer comment son gérer les pointeurs de foctions en RAM ça serait très sympa. Es-ce que le code de la fonction et copier du tas à la pile lors de l’exécution ?

    MERCI À TOUS.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Bktero Voir le message
    PS : comme toute classe de base, elle devrait avoir un destructeur virtuel (ne faisant rien ici).
    Pas forcément :

    Si nous sommes bien dans le cadre d'une interface au sens "javaiste" du terme (comprend : d'une classe dont le seul but est de permettre d'exposer des fonctions dont le comportement doit être défini par celui qui les utilise), tu peux tout à fait envisager de placer le destructeur (non virtuel) dans l'accessibilité protégée, ainsi que le constructeur, selon toute vraisemblance.

    Car il s'agit alors de classes:
    • dont on ne veut pouvoir créer une instance qu'au travers des classes dérivées (ce qui est une raison suffisante pour placer le constructeur dans l'accessibilité protégée
    • dont on veut refuser la création d'une instance en tant que telle (même si la présence d'une fonction virtuelle pure suffirait à cet effet)
    • dont on ne veut surtout pas permettre à l'utilisateur la destruction d'une instance dérivée si elle n'est connue que "en tant qu'instance de la classe de base"

    Ce qui nous donnerait au final quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class MyInterface{
    public:
        /* tant qu'à faire, enfonçons un peu le clou de la sémantique d'entité */
       MyInterface(MyInterface const & ) = delete;
       MyInterface & operator = (MyInterface const &) = delete;
       virtual ReturnType do_something()/* const*/ = 0:
    protected:
       /* seules les classe dérivées auront accès au constructeur et au destructeurs 
        * (qui ne font rien d'autre que leur comportement par défaut, en l'occurrence)
        */
       MyInterface() = default;
       ~MyInterface = default;
    };
    Note d'ailleurs que l'on pourrait s'écarter un peu de cette restriction propre à java (et à d'autres langages) qui veut qu'une interface ne soit composée d'aucune donnée, et que les fonctions que l'on y déclare seront dés lors forcément virtuelles pures.

    Notre interface pourrait, en effet, être représentée sous la forme d'une classe tout à fait classique, avec des données membres qui lui sont propres, et nécessaires au bon fonctionnement des services qu'elle expose; par exemple
    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
    class Movable{
    public:
        /* tant qu'à faire, enfonçons un peu le clou de la sémantique d'entité */
       Movable(Movable const & ) = delete;
       Movable & operator = (Movable const &) = delete;
       /* pas besoin d'une fonction virtuelle : on définit très clairement le comportement */
       void move(Type diffX, Type diffY){
           /* il manque quelques tests "qui vont bien", pour s'assurer que 
            * l'élément se dépalce "où il peut / a la capacité" d'aller ;)
            */
           pos_ = Position{pos_.x + diffX, pos.y + diffY};
       }
       /* on voudra sûrement connaître la position de l'élément */
       Position const & position() const{
           return pos_;
       }
    protected:
       /* on ne veut pas que l'utilisateur de cette classe puisse en créer une instance
        * "telle qu'elle" : il doit passer par une des classes dérivées
        */
       Movable() = default;
       ~Movable() = default;
    private:
        /* la donnée membre qui permet le bon fonctionnement des services exposés */
        Position pos_;
     
    }
    L'énorme avantage, par rapport à la première solution, c'est que l'on ne sera pas obligé de définir le comportement (qui a tout lieu d'être identique) des fonctions exposées pour chaque classe pour laquelle nous souhaitons profiter de la possibilité de mouvement
    @bacelar : merci, cela semble confirmer mon point de vue

    @Matthieu76
    Roh ... ça va c'est juste un cas à rajouter dans le switch quand j'ajoute une fonction c'est pas la mort ! Et puis si on devait vraiment être en parfaite accord avec tout les principes de code on développerait plus rien ... Je vais pas faire un code 10 fois plus compliquer et moins clair et optimiser juste pour ne pas avoir à rajouter 1 ligne dans mon constructeur quand j’ajouterais une fonction tous les 36 du mois.
    C'est justement ce que je tentais d'expliquer: oui, bien sur, ce n'est pas "catastrophique", mais, d'un point de vue purement conceptuel, le simple fait de devoir modifier du code qui fonctionne pour pouvoir rajouter une fonctionnalité est une erreur.

    Or, dans l'ordre "logique" des choses:
    1. on veille d'abord et avant tout à ce que la conception soit correcte
    2. ce n'est qu'une fois que l'on a une conception correcte que l'on prend en compte les spécificités du langage

    Par exemple, ce n'est pas parce que C++ accepte l'héritage multiple, ou parce qu'il permet de déclarer une fonction virtuelle que l'on redéfini dans une accessibilité plus permissive (ou moins permissive) que l'accessibilité dans laquelle elle a été déclarée au départ que l'on peut dire "LSP ne nous permettrait pas cette relation d'héritage, mais comme C++ l'autorise, pourquoi s'en priver "

    En gros, si un principe conceptuel te donne un NO-GO, tu arrête ta réflexion dans la direction que tu avais prise, et tu cherche "une autre solution"
    Nan mais réfléchie 2 secondes, ma classe ne s'appelle pas réellement Algo et n'implémente pas des fonction trigonométriques, mon code est plus complexe que ça. J'ai fait ça pour être plus clair et puis si j'avais expliqué en détail mon code, tout le mode aurait fait des remarques sur le reste de mon code et personne n'aurait répondu à ma question exactement comme tu viens de le faire en critiquant le nom de ma classe. (Je dis ça par expérience )
    A vrai dire, je n'avais pas lu ton message en entier et je m'étais arrêté au code

    Mais tout cela n'est jamais qu'une raison de plus qui devrais t'inciter à respecter les principes SOLID (car j'ai parlé du O, mais les quatre autres ont aussi leur importance )
    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

  17. #17
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par Matthieu76 Voir le message
    Merci j'avais pas pensé à ça, j'avais oublier le mot clé virtual. Du coup est-il possible de faire des méthodes qui prenne en argument une classe "interface" et de passer un enfant en argument ... Euh ... Bah oui évidemment je suis bête !
    Même si dans mon cas cela ne m'aide pas beaucoup car ça serait trop lourd de refaire une classe pour chaque fonction alors que tout le reste de ma classe ne change pas d'un poil.
    C'est tout le concept du pattern Stratégie. Tu as besoin d'un nouveau comportement à passer en paramètre du constructeur de NeuralNetwork ? Et bien tu implémentes une nouvelle fois l'interface PerceptronActivator et sa seule methode activate(). Enfin, ça c'est en Java : en C++, passer un std::function fait très bien l'affaire quand ton interface ne contient qu'une seule méthode !


    [Mode digression]
    Citation Envoyé par koala01
    Pas forcément :
    Oui. Mais bon ta technique ou ma technique, ça reste pour empêcher la destruction polymorphique depuis un pointeur sur le type de base. J'aurais peut-être du précisé : comme elle a une méthode virtuelle, elle devrait avoir un destructeur virtuel.

  18. #18
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    En C++, pour paramétrer une exécution, tu as deux possibilités :
    - Un paramétrage statique, qui sera déterminé au moment où tu écriras le code, résolu à la compilation, et qui peut avoir un coût nul lors de l'exécution.
    - Un paramétrage dynamique, qui peut varier en cours d'exécution, et a un coût (généralement raisonnable, mais un coût néanmoins).

    Pointeur de fonction, switch, design pattern strategy avec appel de fonction virtuelle entrent tous dans la catégorie dynamique (ainsi que std::function, que je préfère généralement aux alternatives déjà évoquées).

    Pour le paramétrage statique, la technique à utiliser est un paramètre template. C'est par exemple ce qui est utilisé pour les algorithmes de la STL (regarde std::sort).

    Pour l'instant je change le contenue de ma fonction directement dans le code puis je recompile.
    Tu es donc dans le cas statique. Souvent, on écrit ce genre de code avec des fonctions, ce qui permet d'utiliser la déduction de type template, et simplifie l'écriture:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<class Function>
    double algo(Function f, double val)
    {
      return f(2*val);
    }
     
    algo([](double d){return sin(d);}, 3.14);
    Mais si tu veux avec une classe :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template<class Function>
    class Algo
    {
      Algo(Function f) : myFunction(f) {}
      double exec(double d) { return myFunction(2*d);}
    }
     
    int g()
    {
        auto f = [](double d) {return sin(d);};
        Algo<decltype(f)> algo(f); // Avec les class template argument deduction du C++ 17, ce code doit pouvoir se simplifier en Algo algo([](double d) {return sin(d);}); mais il faut un compilateur qui gère cette nouveauté
        algo(3.14);
    }
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Oui. Mais bon ta technique ou ma technique, ça reste pour empêcher la destruction polymorphique depuis un pointeur sur le type de base. J'aurais peut-être du précisé : comme elle a une méthode virtuelle, elle devrait avoir un destructeur virtuel.
    Ohoh... Sauf que:

    1- Un constructeur dont tu ne parle pas au compilateur sera implémenté automatiquement sous une forme publique et non virtuelle, ce qui nous empêche d'utiliser la classe en question comme classe de base
    Tout simplement parce qu'un appel à delete myBase; aura pour effet de nous laisser avec un objet partiellement détruit : seule la partie correspondant au type de base sera détruite, mais la partie correspondant à ses classes dérivées (directes et indirectes) ne le sera pas.

    2- Dans le cadre spécifique de la création d'une interface, dont la seule utilité est d'ajouter un (des) comportements à une classe, il ne fait normalement aucun sens de permettre la destructions des instances des classes dérivées alors que nous ne les connaissons que comme des (pointeur sur des) instances de cette interface:

    Pour concrétiser mes dires, reprenons l'exemple d'une interface Movable (quelle que soit la manière dont on la crée). Tu conviendras avec moi qu'il n'y a aucun sens à avoir une fonction qui serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void destroy(Movable * ptr){
        delete ptr;
    }
    Car il n'y a de sens qu'à détruire un objet concret que si on le connait soit comme étant de son type réel (un lion, si c'est un lion, une gazelle si c'est une gazelle) soit si on le connait comme étant du type générique dont il est le digne représentant (une classe Animal, dans ce cas particulier, pour le distinguer des insectes et des plantes).

    3- L'utilisation d'un destructeur publique et virtuel éviterait -- bien sur -- la "destruction partielle" d'un objet, mais, cela ne couvre qu'une partie du problème, car, comme dirait l'autre
    nous devons faire en sorte que nos classes soient facile à utiliser correctement et difficiles à utiliser de manière incorrecte
    En déclarant le destructeur d'une interface comme étant virtuel dans l'accessibilité publique, tu laisses à l'utilisateur de ton interface la possibilité de faire une connerie en l'utilisant; ce qui n'est jamais bon...

    Alors, bien sur, on pourra me rétorquer que "mais personne n'aurait l'idée de détruire un objet alors qu'il ne le connaît sous la forme d'une de ses interfaces", et je serai bien obligé d'admettre que la pratique est pour le moins choquante.

    Malheureusement, la loi de Finagle joue contre nous, et, s'il y a une chose que j'ai apprise depuis longtemps, c'est que l'utilisateur (quel qu'il soit, ce qui inclut tous les utilisateurs des classes que je développe, moi y compris) est un imbécile distrait, et que, si on lui laisse l'occasion de faire une connerie, ce n'est qu'une question de temps avant qu'il ne la fasse.

    Si bien que la seule question intéressante à se poser est "quand va-t-il faire cette connerie", et que la réponse sera toujours la même "au pire moment qui soit possible de trouver". Car il ne sert en effet à rien de perdre son temps à se demander s'il risque ou non effectivement de la faire, vu que la réponse est forcément oui

    Donc, en gros, j'estime que la seule manière vraiment sécurisante de créer une interface (indépendamment du fait que l'on expose des fonctions virtuelles ou non) est bel et bien d'en définir le destructeur dans l'accessibilité protégée, sans qu'il soit déclaré virtuel

    PS: oui, je sais, je suis un vieux con chiant qui s'attarde sur des détails dont tout le monde se fout pas mal... Mais à mon age, on ne me refera plus
    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

  20. #20
    Membre éclairé Avatar de Matthieu76
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Mars 2013
    Messages
    568
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Consultant informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2013
    Messages : 568
    Points : 890
    Points
    890
    Par défaut
    Citation Envoyé par koala01 Voir le message
    le simple fait de devoir modifier du code qui fonctionne pour pouvoir rajouter une fonctionnalité est une erreur.
    Il est vrai que si je mets mon enum dans ma classe statique répertoriant mes fonctions et que j'utilise l'enum comme index d'un vector répertoriant mes fonction je ne devrais plus avoir besoin de modifier ma classe principale pour y ajouter une fonction. merci


    Citation Envoyé par Bktero Voir le message
    Tu implémentes une nouvelle fois l'interface PerceptronActivator et sa seule methode activate().
    C'est bizarre de devoir ré-implémenter une interface alors que je veux juste changer une fonction. Il ne s'agit pas d'un autre type de neurone mais c'est le même neurones avec une fonction d'activate différente. Ça serait illogique d'un point de vue conception d'avoir des 10e de classe du genre : NeuronSinus, NeuronCosinus, NeuronTangent, NeuronSqrt, etc. Même si elle hérite tous d'une même class abstraite Perceptron, c'est plus compréhensible je trouve d'avoir une seule classe qui prend en paramètre un pointeur de fonction. Comme ça je peux mieux regrouper et visualiser mes différente fonction d'activation.


    Citation Envoyé par Bktero Voir le message
    en C++, passer un std::function fait très bien l'affaire quand ton interface ne contient qu'une seule méthode !
    Oui, mais je vais plutôt opter pour des pointeurs de fonction que des std::function car dans mon code je peux créer par exemple 1000 neurones qui utilise tous la même fonction donc autant qu'ils pointent tous sur la même fonction et personnellement je trouve que faire des pointeurs de std::function tout comme des pointeurs de vecteur est complètement absurde


    Citation Envoyé par JolyLoic Voir le message
    template<class Function>
    Merci beaucoup même si je ne comprends pas trop cette ligne de code même si je sais ce qu'est basique un template mais un template de classe alors que c'est une fonction qui varie, pas une classe, je ne comprends pas tout... (J'irais voir plus tard sur internet)

    Et encore merci à tous !

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. [AS2] Atteindre attribut d'une classe depuis une fonction interne a la classe
    Par Demco dans le forum ActionScript 1 & ActionScript 2
    Réponses: 6
    Dernier message: 18/04/2006, 21h03
  2. Comment passer une fonction en argument
    Par Pades75 dans le forum Langage
    Réponses: 4
    Dernier message: 16/02/2006, 10h34
  3. Signature d'une fonction sans argument
    Par cj227854 dans le forum C++
    Réponses: 5
    Dernier message: 20/10/2005, 17h01
  4. creer une fonction avec arguments
    Par niglo dans le forum ASP
    Réponses: 3
    Dernier message: 03/06/2005, 08h04
  5. Passer une fonction comme argument à une fonction
    Par Cocotier974 dans le forum Général Python
    Réponses: 4
    Dernier message: 29/06/2004, 13h41

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