Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 22/04/2008, à 14:19

Le Farfadet Spatial

[Résolu] C++ : problème avec les fonctions virtuelles.

Salut à tous !

   Je dois faire un test de profilage pour voir la différence de performance entre l'utilisation des objets fonctions et des pointeurs de fonctions --- oui, je sais déjà ce que ça va donner, mais on me demande une preuve concrète --- pour l'instant uniquement avec la bibliothèque standard, pour ensuite faire pareil avec Boost. Donc, j'ai commencé un petit programme pour faire le profilage, mais j'ai un problème avec les fonctions virtuelles (j'ai honte). Soit le code suivant :

/**
 * \file main.cpp
 * \brief Exemple d'utilisation des foncteurs en n'utilisant que la
 * bibliothèque standard.
 * \author Yoann LE BARS
 * \date 22 avril 2008
 * \version 1.0
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <functional>
#include <cmath>
#include <cstdlib>
#include <vector>

/**
 * \namespace Fonction
 *
 * Espace de nommage contenant des foncteurs.
 */
namespace Fonction {
  /**
   * \class FonctionSimple
   * \brief Class de base pour les fonctions d'une variable.
   */
  template <class Arg, class Res>
  class FonctionSimple: public std::unary_function<Arg, Res> {
  public:
    /**
     * \brief Surcharge de l'operateur parenthèse.
     *
     * Fonction purement virtuelle.
     */
    Res operator () (const Arg &);
  };  // class FonctionSimple

  /**
   * \class Normale
   * \brief Implementation sous forme de foncteur de la loi normale.
   */
  template <class T> class Normale: public FonctionSimple<T, T> {
    private:
      /// Espérance
      T mu;
      /// Écart type
      T sigma;
  public:
    /**
     * \brief Constructeur par défaut.
     * \param mu_ Initialiseur de l'espérance.
     * \param sigma_ Initialiseur de l'écart type.
     *
     * Si aucune valeur n'est donnée, initialisation à zéro.
     */
    Normale (const T &mu_ = static_cast<T>(0),
                  const T &sigma_ = static_cast<T>(0)):
      mu (mu_), sigma (sigma_) {}  // Gaussienne (const T &, const T &)

    /**
     * \brief Donne l'espérance.
     * \return mu
     *
     * La modification n'est pas possible.
     */
    T esperance (void) const throw () {return mu;}
    /**
     * \brief Donne l'écart type.
     * \return sigma
     *
     * La modification n'est pas possible.
     */
    T ecartType (void) const throw () {return sigma;}
    /**
     * \brief Donne l'espérance.
     * \return mu
     *
     * La modification est possible.
     */
    T &esperance (void) throw () {return mu;}
    /**
     * \brief Donne l'écart type.
     * \return sigma
     *
     * La modification est possible.
     */
    T &ecartType (void) throw () {return sigma;}

    /**
     * \brief Surcharge de l'opérateur parenthèse.
     * \param x Valeur dont on veut connaître la probabilité.
     */
    T operator () (const T &x) const throw () {
      const T facteur =
        static_cast<T>(1) / (sigma * static_cast<T>(std::sqrt(2. * M_PI)));
      const T numerateur = x - mu;
      return facteur * std::exp(-(numerateur * numerateur)
				         / (static_cast<T>(2) * sigma * sigma));
    }  // T operator () (const Arg &) const
  };  //  class Normale
}  // namespace Fonction

/**
 * \namespace Utile
 *
 * Espace de nommage comprenant des fonctions utilitaires.
 */
namespace Utile {
  /**
   * \brief Tire une valeur aléatoire au sort.
   * \param min Valeur minimum.
   * \param max Valeur maximum.
   * \return Une valeur aléaatoire dans [min ; max].
   */
  template <class T> inline T aleatoire (const T &min, const T &max) throw () {
    return
      static_cast<T>(std::rand()) / static_cast<T>(RAND_MAX) * (max - min) + min;
  }  // template <class T> T aleatoire (const T &, const T &)
}  // namespace Utile

/**
 * \brief Corps du programme.
 * \param argc Nombre d'arguments dans la ligne de commande.
 * \param argv La ligne de commande.
 * \return 0 si tout va bien, -1 si une quelconque exception est déclanchée.
 */
int main(int argc, char **argv) {
  try {
    std::srand(static_cast<unsigned int>(std::time(NULL)));

    const double mu = Utile::aleatoire(0., 100.);
    const double sigma = Utile::aleatoire(0., 100.);
    const Fonction::Normale<double> f (mu, sigma);

    std::vector<double> x (1000);
    for (size_t i = 0; i < x.size(); ++i) {
      x[i] = Utile::aleatoire(0., 100.);
      std::cout << f(x[i]) << std::endl;
    }

    return 0;
  }
  catch (const std::exception &e) {
    std::cerr << "Exception caught: " << e.what() << std::endl;
    return -1;
  }
}  // main

Celui-ci fonctionne comme je l'attends. Par contre, si je change la définition de la classe FonctionSimple pour transformer l'opérateur en fonction virtuelle, j'obtiens une erreur à l'édition des liens. La classe modifiée :

template <class Arg, class Res>
class FonctionSimple: public std::unary_function<Arg, Res> {
public:
  /**
   * \brief Surcharge de l'operateur parenthèse.
   *
   * Fonction purement virtuelle.
   */
  virtual Res operator () (const Arg &);
};  // class FonctionSimple

L'erreur alors obtenue à l'édition des liens :

Building CXX object CMakeFiles/exemplefoncteur.dir/main.o
/usr/bin/c++   -ansi -pedantic -Wall -g -o CMakeFiles/exemplefoncteur.dir/main.o -c /home/lebars/test/exemplefoncteur/main.cpp
Linking CXX executable exemplefoncteur
/usr/bin/cmake -P CMakeFiles/exemplefoncteur.dir/cmake_clean_target.cmake
/usr/bin/c++      -fPIC  -L /home/softs/fftpack/lib/ -L /home/softs/fftpack/lib/ "CMakeFiles/exemplefoncteur.dir/main.o"   -o exemplefoncteur -rdynamic
CMakeFiles/exemplefoncteur.dir/main.o:(.rodata._ZTVN8Fonction7NormaleIdEE[vtable for Fonction::Normale<double>]+0x10): undefined reference to `Fonction::FonctionSimple<double, double>::operator()(double const&)'
CMakeFiles/exemplefoncteur.dir/main.o:(.rodata._ZTVN8Fonction14FonctionSimpleIddEE[vtable for Fonction::FonctionSimple<double, double>]+0x10): undefined reference to `Fonction::FonctionSimple<double, double>::operator()(double const&)'
collect2: ld returned 1 exit status

J'avoue que je ne comprends pas pourquoi cette erreur se déclare. Si vous avez une idée, à vot' bon cœur !

   À bientôt.

                                                                                                                     Le Farfadet Spatial

Dernière modification par Le Farfadet Spatial (Le 24/04/2008, à 11:31)

Hors ligne

#2 Le 22/04/2008, à 15:39

botojo

Re : [Résolu] C++ : problème avec les fonctions virtuelles.

La surcharge d'opérateurs ne peut pas être virtuel. 

Enfin à ma connaissance!

Ce que je suis certain c'est qu'un constructeur ne peut pas être virtuel.

Enfin creuse, mais comme ton message est pas mal technique, je pense que tu pourras avoir plus de chance dans un forum de programmation.

a+

Botojo

Hors ligne

#3 Le 22/04/2008, à 15:46

Luc Hermitte

Re : [Résolu] C++ : problème avec les fonctions virtuelles.

Les foncteurs sont manipulés par valeur dans toute la SL. Le virtuel est complètement de trop du coup, il perd beaucoup de sens.

Sinon, je sais qu'il y a des règles bizarres entre la virtualité et les inlines, règles que je n'ai pas vraiment creusé. A ce sujet, j'ai peut-être manqué un truc, mais je n'ai pas eu l'impression que ton code était complet.

PS: throw() devrait te dégrader les perfs si ton compilo, conformément à ce qu'on lui demande, vérifie bien que rien ne s'échappe.
PPS: pose plutôt la question sur developpez ou fclc++

#4 Le 22/04/2008, à 16:28

Karl_le_rouge

Re : [Résolu] C++ : problème avec les fonctions virtuelles.

Un constructeur ne peut pas être virtuel car le compilo rajoute du code justement pour initialiser les vtables. Par contre un opérateur peut être virtuel.
Il me semble qu'en revanche, une méthode template ne peut être virtuelle ?

Hors ligne

#5 Le 22/04/2008, à 17:01

Le Farfadet Spatial

Re : [Résolu] C++ : problème avec les fonctions virtuelles.

Salut à tous !

   Merci de vos réponses.

Luc Hermitte a écrit :

A ce sujet, j'ai peut-être manqué un truc, mais je n'ai pas eu l'impression que ton code était complet.

Tu es très observateur ! En effet, le code que j'ai déposé dans ce sujet n'est pas mon code complet et, s'il compile (lorsque rien n'est virtuel), il ne fait rien. Je voulais juste isoler mon problème tout en vous donnant la hierarchie que j'ai commencé à mettre en place, sans plus noyer le poisson que ça, vu que c'est déjà assez long.

Luc Hermitte a écrit :

Les foncteurs sont manipulés par valeur dans toute la SL. Le virtuel est complètement de trop du coup, il perd beaucoup de sens.

C'est-à-dire que l'objectif est aussi d'utiliser les foncteurs dans des parties de codes autres que ceux de la bibliothèque standard. Typiquement, certains calculs peuvent être fait à partir de deux fonctions différentes (en fonction de la précision voulue), de sorte qu'il faut pouvoir transmettre à une procédure la fonction à utiliser. Une approche serait d'utiliser les pointeurs de fonctions, mais il devient alors délicat de faire du polymorphisme et, de plus, ça peut vite devenir moins clair et moins performant qu'un foncteur.

   Je serais plutôt adepte de transmettre un foncteur par référence, d'où l'idée d'une classe de base FonctionSimple, avec un operateur parenthèse purement virtuel. L'idée, à terme, ce serait même d'avoir quelque chose comme :

template <class Arg, class Res>
class FonctionSimple: public std::unary_function<Arg, Res> {
public:
  /**
   * \brief Surcharge de l'operateur parenthèse.
   *
   * Fonction purement virtuelle.
   */
  virtual Res operator () (const Arg &) = 0;
};  // class FonctionSimple

Et qu'une procédure quelconque puisse utiliser mon foncteur. Par exemple :

template <class Arg, class Res>
void test (const Fonction::FonctionSimple<Arg, Res> &f,
              const std::vector<Arg> &x) {
  for (size_t i = 0; i < x.size(); ++i) std::cout << f(x[i]) << std::endl;
}  /* template <class Arg, class Res> void test (const std::unary_function<Arg, Res> &,
                                                                   const std::vector<Arg> &) */

J'espère que cela explique plus précisément ce que je cherche à faire.

botojo a écrit :

La surcharge d'opérateurs ne peut pas être virtuel.

Enfin à ma connaissance!

Ah bon ? Pourtant, mes informations iraient plutôt dans ce sens :

Karl_le_rouge a écrit :

Un constructeur ne peut pas être virtuel car le compilo rajoute du code justement pour initialiser les vtables. Par contre un opérateur peut être virtuel.

De toute façon, quant à cette remarque et celle-ci :

Karl_le_rouge a écrit :

Il me semble qu'en revanche, une méthode template ne peut être virtuelle ?

Si tel étais le cas, je pense que j'aurais une erreur à la compilation et non pas à l'édition des liens. Du coup, je pencherais plutôt pour ça :

Luc Hermitte a écrit :

Sinon, je sais qu'il y a des règles bizarres entre la virtualité et les inlines

Il va falloir que je me renseigne là-desssus.

   Pour finir, sur deux dernières remarques :

botojo a écrit :

je pense que tu pourras avoir plus de chance dans un forum de programmation.

Et :

Luc Hermitte a écrit :

pose plutôt la question sur developpez ou fclc++

C'était un peu mon premier réflexe, mais je manque un peu de temps pour suivre plusieurs forums à la fois. Cela dit, je vais sans doute m'y résoudre.

Luc Hermitte a écrit :

throw() devrait te dégrader les perfs si ton compilo, conformément à ce qu'on lui demande, vérifie bien que rien ne s'échappe.

C'est intéressant, car j'ai eu un autre son de cloche. De toute façon, en général, j'essaye de bien déterminer quelles exceptions peuvent être envoyées. Cela dit, je prends ta remarque en considération.

   À bientôt.

                                                                                                                     Le Farfadet Spatial

Hors ligne

#6 Le 22/04/2008, à 18:08

Luc Hermitte

Re : [Résolu] C++ : problème avec les fonctions virtuelles.

Attention aux foncteurs par référence. Le fait qu'ils soient passés par valeur n'est pas innocent à mon avis. C'est une des règles conseillée dans Effective STL de Scott Meyers. En diagonale, je lis que la SL prend par valeur pour pouvoir accepter pointeur de fonction comme foncteur sans broncher. Puis toute une discussion sur le slicing qui attend au tournant et que donc pour avoir du polymorphisme, il faut introduire une indirection à la pseudo-pimpl, vu que le type d'implémentation sera celui dérivé.

Après, je ne sais pas si cela a des impacts sur la capacité du compilo à inliner l'appel. Il faudrait faire des tests et décoder l'assembleur généré.



Pour le throw(), j'ai l'impression qu'il y a eu une légion de personnes qui se sont auto-convaincues que le compilo allait comprendre un truc, sans même comprendre ce qu'implique une spécification d'exception -> vérifications à l'exécution (=> lenteur) afin de pouvoir interrompre subitement (et sans échappatoire) le programme. Soit un truc difficilement acceptable dans un produit livré.

http://pkisensee.spaces.live.com/Blog/cns!3C84486A9D832EB7!167.entry

#7 Le 22/04/2008, à 18:30

Le Farfadet Spatial

Re : [Résolu] C++ : problème avec les fonctions virtuelles.

Salut à tous !

Luc Hermitte a écrit :

Attention aux foncteurs par référence. Le fait qu'ils soient passés par valeur n'est pas innocent à mon avis. C'est une des règles conseillée dans Effective STL de Scott Meyers. En diagonale, je lis que la SL prend par valeur pour pouvoir accepter pointeur de fonction comme foncteur sans broncher. Puis toute une discussion sur le slicing qui attend au tournant et que donc pour avoir du polymorphisme, il faut introduire une indirection à la pseudo-pimpl, vu que le type d'implémentation sera celui dérivé.

Après, je ne sais pas si cela a des impacts sur la capacité du compilo à inliner l'appel. Il faudrait faire des tests et décoder l'assembleur généré.

Bref, cela mérite une réelle discussion, donc il faut vraiment que j'en parle sur developpez.com !

Pour le throw(), j'ai l'impression qu'il y a eu une légion de personnes qui se sont auto-convaincues que le compilo allait comprendre un truc, sans même comprendre ce qu'implique une spécification d'exception -> vérifications à l'exécution (=> lenteur) afin de pouvoir interrompre subitement (et sans échappatoire) le programme. Soit un truc difficilement acceptable dans un produit livré.

Ne t'en fais pas : tu m'avais convaincu dès ta première remarque là-dessus !

   À bientôt.

                                                                                                                     Le Farfadet Spatial

Hors ligne

#8 Le 24/04/2008, à 11:30

Le Farfadet Spatial

Re : [Résolu] C++ : problème avec les fonctions virtuelles.

Salut à tous !

   Je suis passé sur le forum Developpez.com et j'ai ainsi pu trouver où se situait le problème : il s'agissait d'une incompatibilité de prototype entre la surcharge de l'opérateur parenthèse dans la classe mère et dans la classe fille. J'y ai également trouvé des éléments de réflexion intéressants pour essayer de déterminer l'approche la plus approprié à mon problème. Vous pourrez trouver ce fil de discussion à cette adresse : http://www.developpez.net/forums/showth … p?t=534573.

   Voici comment se présente mon code désormais :

/**
 * \file main.cpp
 * \brief Exemple d'utilisation des foncteurs en n'utilisant que la
 * bibliothèque standard.
 * \author Yoann LE BARS
 * \date 22 avril 2008
 * \version 1.0
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <functional>
#include <cmath>
#include <cstdlib>
#include <vector>

/**
 * \namespace Fonction
 *
 * Espace de nommage contenant des foncteurs.
 */
namespace Fonction {
  /**
   * \class FonctionSimple
   * \brief Class de base pour les fonctions d'une variable.
   */
  template <class Arg, class Res>
  class FonctionSimple: public std::unary_function<Arg, Res> {
  public:
    /**
     * \brief Surcharge de l'operateur parenthèse.
     *
     * Fonction purement virtuelle.
     */
    virtual Res operator () (const Arg &) const = 0;
  };  // class FonctionSimple

  /**
   * \class Normale
   * \brief Implémentation sous forme de foncteur de la loi normale.
   */
  template <class T> class Normale: public FonctionSimple<T, T> {
    private:
      /// Espérance
      T mu;
      /// Écart type
      T sigma;
  public:
    /**
     * \brief Constructeur par défaut.
     * \param mu_ Initialiseur de l'espérance.
     * \param sigma_ Initialiseur de l'écart type.
     *
     * Si aucune valeur n'est donnée, initialisation à zéro.
     */
    Normale (const T &mu_ = static_cast<T>(0),
	     const T &sigma_ = static_cast<T>(0)):
      mu (mu_), sigma (sigma_) {}  // Normale (const T &, const T &)

    /**
     * \brief Donne l'espérance.
     * \return mu
     *
     * La modification n'est pas possible.
     */
    T esperance (void) const {return mu;}
    /**
     * \brief Donne l'écart type.
     * \return sigma
     *
     * La modification n'est pas possible.
     */
    T ecartType (void) const {return sigma;}
    /**
     * \brief Donne l'espérance.
     * \return mu
     *
     * La modification est possible.
     */
    T &esperance (void) {return mu;}
    /**
     * \brief Donne l'écart type.
     * \return sigma
     *
     * La modification est possible.
     */
    T &ecartType (void) {return sigma;}

    /**
     * \brief Surcharge de l'opérateur parenthèse.
     * \param x Valeur dont on veut connaître la probabilité.
     */
    virtual T operator () (const T &x) const {
      const T facteur =
	static_cast<T>(1) / (sigma * static_cast<T>(std::sqrt(2. * M_PI)));
      const T numerateur = x - mu;
      return facteur * std::exp(-(numerateur * numerateur)
				/ (static_cast<T>(2) * sigma * sigma));
    }  // T operator () (const Arg &) const
  };  //  class Normale
}  // namespace Fonction

/**
 * \namespace Utile
 *
 * Espace de nommage comprenant des fonctions utilitaires.
 */
namespace Utile {
  /**
   * \brief Tire une valeur aléatoire au sort.
   * \param min Valeur minimum.
   * \param max Valeur maximum.
   * \return Une valeur aléatoire dans [min ; max].
   */
  template <class T> inline T aleatoire (const T &min, const T &max) {
    return
      static_cast<T>(std::rand()) / static_cast<T>(RAND_MAX) * (max - min)
      + min;
  }  // template <class T> T aleatoire (const T &, const T &)

  /**
   * \brief Appelle une fonction sur un vecteur de valeurs et écrit les
   * résultats sur la sortie standard.
   * \param f La fonction.
   * \param x Le vecteurs des valeurs.
   */
  template <class Arg, class Res>
  void test (const Fonction::FonctionSimple<Arg, Res> &f,
	     const std::vector<Arg> &x) {
    for (size_t i = 0; i < x.size(); ++i) std::cout << f(x[i]) << std::endl;
  }  /* template <class Arg, class Res>
	void test (const Fonction::FonctionSimple<Arg, Res> &,
		   const std::vector<Arg> &) */
}  // namespace Utile

/**
 * \brief Corps du programme.
 * \param argc Nombre d'arguments dans la ligne de commande.
 * \param argv La ligne de commande.
 * \return 0 si tout va bien, -1 si une quelconque exception est déclanchée.
 */
int main(int argc, char **argv) {
  try {
    std::srand(static_cast<unsigned int>(std::time(NULL)));

    std::vector<double> x (1000000);
    for (size_t i = 0; i < x.size(); ++i) x[i] = Utile::aleatoire(0., 100.);

    const double mu = Utile::aleatoire(0., 100.);
    const double sigma = Utile::aleatoire(0., 100.);

    Utile::test(Fonction::Normale<double> (mu, sigma), x);

    return 0;
  }
  catch (const std::exception &e) {
    std::cerr << "Exception interceptée : " << e.what() << std::endl;
    return -1;
  }
}  // main

Je passe donc le sujet en résolu. Merci de votre aide.

   À bientôt.

                                                                                                                     Le Farfadet Spatial

Hors ligne