IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
logo
Sommaire > Les fonctions
        Pourquoi certaines fonctions membres possèdent le mot clé const après leur nom ?
        Quelle est la particularité d'une fonction membre static ?
        Qu'est-ce que le masquage de fonction ?
        Pourquoi ne peut-on pas passer une fonction membre là où on attend un pointeur de fonction ?
        Quel est l'équivalent C++ des paramètres variables ?
        Comment passer correctement des paramètres à ma fonction ?
        La fonction de ma classe entre en conflit avec une fonction standard, que faire ?
        Quelles précautions faut-il prendre avec les fonctions callback ?
        Pourquoi ne faut-il qu'un seul return par fonction ?
7.1. Les fonctions membres virtuelles (6)
                Que signifie le mot-clé virtual ?
                Pouvez-vous me donner une raison simple pour laquelle la virtualité est si importante ?
                Les fonctions virtuelles sont-elles un mécanisme important en C++ ?
                Qu'est-ce qu'une fonction virtuelle pure ?
                Qu'est-ce qu'un type de retour covariant ?
                Puis-je appeler des fonctions virtuelles dans le constructeur (ou le destructeur) ?
7.2. Les fonctions inline (8)
                Que se passe-t-il avec les fonctions inline ?
                Un exemple simple d'intégration
                Les fonctions inline améliorent-elles les performances ?
                Comment les fonctions inline peuvent-elles influer sur le compromis vitesse/sécurité ?
                Pourquoi utiliser une fonction inline au lieu d'une macro #define ?
                Comment signaler au compilateur de mettre une fonction non membre inline ?
                Comment signaler au compilateur de mettre une fonction membre inline ?
                Y a-t-il un autre moyen de spécifier une fonction membre inline ?



Pourquoi certaines fonctions membres possèdent le mot clé const après leur nom ?
Créé le 22/11/2004[haut]
auteur : Aurélien Regat-Barrel
Quand une fonction membre (non statique) d'une classe ne modifie pas cette dernière, il est judicieux en C++ de la rendre constante en ajoutant le mot-clé const à la fin de son prototype. Cela rappelle que cette fonction ne modifie et ne doit pas modifier l'objet ce qui permet de l'utiliser sur des objets constants en plus d'aider le compilateur à effectuer des optimisations.
class Test
{
public:
    std::string F() const
    {
        return "F() const";
    }

    std::string F()
    {
        return "F()";
    }
};

int main()
{
    Test t1;
    cout << t1.F() << '\n'; // affiche "F()"

    const Test & t2 = t1;
    cout << t2.F() << '\n'; // affiche "F() const"
}
Dans l'exemple précédent, si la fonction membre F() const n'existait pas, on n'aurait pas pu appeler F() sur l'objet t2.
Notez que le fait d'avoir rajouté le mot clé const a provoqué une surcharge de la fonction F() au même titre qu'une surcharge effectuée avec un nombre de paramètres différents.


Quelle est la particularité d'une fonction membre static ?
Mise à jour le 19/10/2004[haut]
auteurs : LFE, Aurélien Regat-Barrel
Une fonction membre déclarée static a la particularité de pouvoir être appelée sans devoir instancier la classe.
Elle ne peut utiliser que des variables et des fonctions membres static elles aussi, c'est-à-dire qui ont une existence en dehors de toute instance.
class A
{
public:
    // variable et fonction non statiques
    int var1;
    void f1() {};
    // variable et fonction statiques
    static int var2;
    static void f2() {};
};

// IMPORTANT : il faut définir la variable static
int A::var2 = 0;

int main()
{
    A a; // instance de A
    // var1 et f1 nécessitent une instance de A
    a.var1 = 1;
    a.f1();

    // var2 et f2 sont static et n'ont pas besoin d'instance
    A::var2 = 1;
    A::f2();
}

Qu'est-ce que le masquage de fonction ?
Créé le 17/10/2005[haut]
auteur : Laurent Gomila
On parle de masquage de fonction lorsqu'on définit dans une classe dérivée une fonction de même nom qu'une fonction d'une classe de base, mais avec un prototype différent. Voici un exemple qui illustre ce problème :

#include <iostream>
#include <string>

struct Base
{
    void F(int);
};

struct Derivee : Base
{
    void F(std::string);
};

Derivee d;
d.F("salut"); // Ok : appelle Derivee::F
d.F(5); // Erreur :  aucune fonction "F" prenant un int
Dans cet exemple, la fonction F de la classe de base n'est non pas surchargée mais masquée, ce qui signifie qu'elle n'est plus accessible dans la classe dérivée. Pour palier ce problème il suffit d'utiliser la directive using pour réimporter la fonction masquée dans la portée de la classe dérivée :


struct Derivee : Base
{
    using Base::F;

    void F(std::string s);
};

Derivee d;
d.F("salut"); // Ok : appelle Derivee::F
d.F(5); // Ok : appelle Base::F
On peut également régler le problème en spécifiant explicitement lors de l'appel d'où vient la fonction que l'on souhaite utiliser :

Derivee d;
d.Base::F(5); // Ok : appelle Base::F

Pourquoi ne peut-on pas passer une fonction membre là où on attend un pointeur de fonction ?
Créé le 22/11/2004[haut]
auteur : Laurent Gomila
En C++, il est possible de passer des pointeurs de fonctions en paramètre d'autres fonctions. Mais peut-être aurez-vous remarqué que le compilateur râle parfois lorsque vous essayez de passer un pointeur sur fonction membre. Voici un exemple courant, la création de threads (sous Windows) :
DWORD WINAPI Fonction1( void *Param ) // Fonction globale 
{ 
    return 0; 
} 

DWORD WINAPI MaClasse::Fonction2( void *Param ) // Fonction membre de MaClasse 
{ 
    return 0; 
} 

MaClasse Param; 
CreateThread( NULL, 0, Fonction1, &Param, 0, NULL ); // OK 
CreateThread( NULL, 0, &MaClasse::Fonction2, &Param, 0, NULL ); // Erreur !
Pourquoi ce code ne compile pas avec une fonction membre ? Parce que le type de Fonction1 et MaClasse::Fonction2 n'est pas le même. La fonction globale Fonction1 a pour type DWORD (*)(void*).
La fonction membre Fonction2 a pour type DWORD (MaClasse::*)(void*).
On comprend facilement cette différence, étant donné que Fonction2 aura besoin d'une instance de MaClasse pour être appelée, au contraire de Fonction1 qui pourra être appelée "librement". A noter que le type des fonctions membres statiques peut être assimilé à celui des fonctions globales, puisque celles-ci peuvent être également appelées sans instance de la classe. Ainsi pour contourner le problème, il faudrait (par exemple) procéder ainsi :
class MaClasse 
{ 
public : 

    static DWORD WINAPI StaticThreadFunc( void *Param ) 
    { 
        MaClasse* Obj = reinterpret_cast<MaClasse*>( Param ); 
        return Obj->ThreadFunc(); 
    } 

private : 

    DWORD ThreadFunc() 
    { 
        // ... 
        return 0; 
    } 
}; 

MaClasse Param; 
CreateThread( NULL, 0, &MaClasse::StaticThreadFunc, &Param, 0, NULL );
A noter qu'on peut tout à fait demander à une fonction de recevoir comme paramètre un pointeur sur fonction membre, il suffit d'indiquer le bon type, comme expliqué ci-dessus.


Quel est l'équivalent C++ des paramètres variables ?
Créé le 17/10/2005[haut]
auteur : Laurent Gomila
En C, il est possible de déclarer une fonction acceptant un nombre de paramètres variables via "..." (c'est ce qu'on appelle l'ellipse). L'exemple le plus connu est celui de la fonction d'affichage printf.

En C++ il est bien entendu toujours possible d'utiliser cette méthode mais il y a mieux : le chaînage d'appels. Les avantages sont multiples :

  • Typage beaucoup plus fort.
  • Pas besoin de manipuler des macros bizarroïdes pour récupérer les paramètres.
  • Pas besoin d'indication supplémentaire pour marquer le nombre et le type des paramètres.
  • Beaucoup plus simple à écrire, et plus flexible.
Cette méthode est intensivement utilisée par exemple pour manipuler les flux standards :

#include <iostream>

std::cout << x << y << z;
On peut également imaginer d'autres formes de chaînages pour d'autres applications :

#include <vector>

class Polygon
{
pulic :

    Polygon& Add(int x, int y)
    {
        Points_.push_back(Point(x, y));

        return *this;
    }

private :

    std::vector<Point> Points_;
};

Polygon Poly;
Poly.Add(1, 2)
    .Add(5, 8)
    .Add(15, 19)
    .Add(0, 54);

#include <sstream>
#include <string>

class StringBuilder
{
pulic :

    template <typename T>
    StringBuilder& operator ()(const T& t)
    {
        std::ostringstream oss;
        oss << t;
        String_ += oss.str();

        return *this;
    }

private :

    std::string String_;
};

StringBuilder s;
s("salut j'ai ")(24)(" ans ");
Comme vous le voyez, ce qui rend possible le chaînage est le renvoi de l'instance courante par la fonction. Ainsi, Poly.Add(x, y) renvoie Poly, sur lequel on peut de nouveau appeler Add etc...


Comment passer correctement des paramètres à ma fonction ?
Créé le 17/10/2005[haut]
auteur : Laurent Gomila
Par défaut les paramètres de fonctions sont passés par valeur, c'est-à-dire que c'est une copie du paramètre passé qui est manipulée par la fonction et non l'original. Cela peut paraître anodin lorsqu'il s'agit de passer un type de base, mais cela devient vite pénalisant lorsqu'il s'agit d'une instance de classe dont la copie peut s'avérer coûteuse (par exemple un vector de string). Cela peut également être un problème si l'on souhaite passer en paramètre une classe qui n'est tout simplement pas copiable (par exemple un flux standard). Pour régler le problème, on utilise ainsi ce qu'on appelle le passage par référence ou par référence constante. En passant une référence, on s'assure que c'est l'objet initial qui est manipulé dans la fonction et donc qu'aucune recopie indésirable n'est effectuée. En passant une référence constante, on s'assure également que notre paramètre ne pourra pas être modifié par la fonction. Une bonne habitude est donc de prendre tout paramètre non modifiable par référence constante, excepté les types primitifs. D'autant plus que cela n'a strictement aucune autre conséquence, ni au niveau de la fonction ni au niveau de l'appelant.

#include <string>
#include <vector>

void FonctionPasOptimisee(std::vector<std::string> Tab)
{
    // ...
}

void FonctionOptimisee(const std::vector<std::string>& Tab)
{
    // ...
}

std::vector<std::string> v(5000, std::string(1000, '-')); // Tableau de 5000 chaînes de 1000 caractères

FonctionPasOptimisee(v); // recopie inutilement nos 5 millions de caractères
FonctionOptimisee(v); // ne recopie rien du tout
Attention à ne pas oublier le const si le paramètre n'est pas modifié dans la fonction : cela permet en effet de passer ce que l'on appelle des temporaires non nommés.

#include <string>
#include <vector>

void FonctionIncorrecte(std::string& s)
{
    // ...
}

void FonctionCorrecte(const std::string& s)
{
    // ...
}

std::string s1 = "salut";
std::string s2 = "hello";

FonctionIncorrecte(s1); // Ok
FonctionCorrecte(s1); // Ok

FonctionIncorrecte(s1 + s2); // Erreur : s1 + s2 est un temporaire
FonctionCorrecte(s1 + s2); // Ok

FonctionIncorrecte("bonjour"); // Erreur : "bonjour" est un temporaire
FonctionCorrecte("bonjour"); // Ok
Cette remarque vaut également pour les pointeurs :

void FonctionIncorrecte(char* s)
{
    // ...
}

void FonctionCorrecte(const char* s)
{
    // ...
}

FonctionIncorrecte("bonjour"); // Erreur : "bonjour" est un temporaire
FonctionCorrecte("bonjour"); // Ok

La fonction de ma classe entre en conflit avec une fonction standard, que faire ?
Créé le 20/04/2003[haut]
auteur : LFE
Définir dans une classe une fonction membre qui a le même nom qu'une fonction standard est possible, mais risque de poser problème lors de l'utilisation de cette fonction membre à l'intérieur de la classe. La fonction membre masque la fonction standard.
Il reste toutefois possible d'utiliser la fonction standard en faisant précéder son nom de l'opérateur de résolution de portée ::.
class MaClasse
{
    int abs(int x); // masque la fonction standard abs
}

int MaClasse::abs(int x)
{
    return ::abs(x); // fait appel à la fonction standard abs()
}

Quelles précautions faut-il prendre avec les fonctions callback ?
Créé le 15/10/2009[haut]
auteur : Aurélien Regat-Barrel
En C++, en général, on évite d'utiliser les fonctions callback au profit d'alternatives un peu plus "objet" (tel les foncteurs par exemple). Cependant, on est parfois obligé d'y recourir, typiquement pour s'interfacer avec une autre bibliothèque écrite en C. Si tel est votre cas, il vous faut alors être prudent et veiller à ce que votre fonction callback C++ passée à la bibliothèque C ne lève pas d'exception, surtout si vous l'utilisez déja compilée (dll). La raison est que les exceptions C++ ne sont pas supportée en C, et les conséquences d'une exception levée dans votre callback C++ et remontant jusqu'au code C appelant peuvent étre fâcheuses. Et d'une manière plus générale, les exceptions posent problème dès qu'il s'agit de franchir les limites d'un module compilé (telle une dll), même entre differents modules développés en C++ (ABI incompatibles).

On peut toutefois préciser que sous Windows, un système d'exceptions (Structured Exception Handling, ou SEH) est integré au sein meme du système. Certains compilateurs l'exploitent, y compris en langage C (au moyen de mots clés spécifiques), ce qui permet à du code C d'être traversé sans problème par des exceptions lancées depuis un code C++, si celles-ci ont été émises sous forme de SEH. Certains compilateurs s'appuient sur SEH pour implementer leurs exceptions C++ (c'est le cas de Visual C++ par exemple), les rendant ainsi compatibles avec n'importe quel autre code compilé. Consultez la documentation de votre compilateur pour plus de détails.


Pourquoi ne faut-il qu'un seul return par fonction ?
Créé le 15/10/2009[haut]
auteur : white_tentacle
Parce qu'on vous a menti !
Cette pratique provient d'anciens langages de programmation, qui ne disposaient pas de mécanismes élégants pour la gestion de la mémoire. Ainsi, on n'avait coutume d'écrire des choses qui ressemblent à ça :
 
    int f()
    {
       char * my_string = (char*) malloc(20 * sizeof(char) );
       int result;
       /* début du traitement complexe */
           if(error)
           {
              result = -1;
              goto end_f;
           }
       /* fin du traitement complexe */
    :end_f
        free(my_string);
        return result;
    }
     
Ceci permettait de gérer toute la libération mémoire dans un seul bloc de code. Mais pour passer dans ce bloc de code à coup sûr, il était nécessaire d'utiliser, soit des imbrications de if à n'en plus finir, soit des gotos. Les return multiples étaient sources de nombreuses fuites mémoires, difficiles à diagnostiquer.
Heureusement, C++ possède un mécanisme très puissant, le destructeur. Ainsi, le code précédent s'écrira :
 
    int f()
    {
       std::string my_string;
       /* début du traitement complexe */
           if(error)
                return -1;
       /* fin du traitement complexe, qui contient maintenant le return */
    }
     
Plus besoin de gérer l'allocation mémoire, elle sera libérée par le destructeur de std::string. Et si l'on est obligé d'allouer avec new, C++ nous offre là encore un moyen simple de gérer celà, std::auto_ptr :
 
    int f()
    {
       std::auto_ptr<Obj> local_object = new CMyClass();
      // reste de la fonction
    }
     
local_object sera libéré (appel de delete) dès que l'on sort de la portée de la variable.
Et pour les objets alloués avec new[] ? C++ ne fournit pas en standard de moyen... sauf d'utiliser plutôt std::vector ! Et comme souvent, si ce n'est pas possible, Boost vient à la rescousse avec boost::scoped_array.
Et pour les objets dont l'allocation est faite par une fonction d'une bibliothèque, à libérer en appelant une autre fonction de cette bibliothèque ? C++ nous offre la possibilité d'utiliser des scope_guard, qui appelleront automatiquement la fonction de libération lors de leur destruction.
Il n'y a donc de nos jours plus aucune raison de se priver de la possibilité d'utiliser plusieurs instructions return, lorsque cela apporte plus de clarté et de lisibilité au code.

lien : faq Comment créer et utiliser un tableau avec std::vector ?
lien : faq Qu'est-ce qu'un pointeur intelligent ?
lien : faq Comment gérer proprement des allocations / désallocations de ressources ? Le RAII !
lien : fr  Boost.SmartPtr : les pointeurs intelligents de Boost, par Matthieu Brucher


Consultez les autres F.A.Q.


Valid XHTML 1.0 TransitionalValid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2008 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.