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 precautions faut-il prendre avec les fonction callback ?
        6.1. Les fonctions membres virtuelles (7)
                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 ?
                Précautions à prendre avec les appels de fonctions virtuelles dans un constructeur ou un destructeur
                Puis-je appeler des fonctions virtuelles dans le constructeur (ou le destructeur)
        6.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 ?

rechercher
precedent    sommaire    suivant    telechargermiroir


Pourquoi certaines fonctions membres possèdent le mot clé const après leur nom ?
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 ?
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 ?
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 ?
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 ?
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 ?
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 ?
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 precautions faut-il prendre avec les fonction callback ?
auteur : Aurélien Regat-Barrel
En C++, en general, on evite d'utiliser les fonctions callback au profit d'alternatives un peu plus "objet" (tel les foncteurs par exemple). Cependant, on est parfois oblige d'y recourir, typiquement pour s'interfacer avec une autre bibliotheque ecrite en C. Si tel est votre cas, il vous faut alors etre prudent et veiller a ce que votre fonction callback C++ passee a la bibliotheque C ne leve pas d'exception, surtout si vous l'utilisez deja compilee (dll). La raison est que les exceptions C++ ne sont pas supportee en C, et les consequences d'une exception levee dans votre callback C++ et remontant jusqu'au code C appelant peuvent etre facheuses. Et d'une maniere plus generale, les exceptions posent probleme des qu'il s'agit de franchir les limites d'un module compile (tel une dll), meme entre differents modules developpes en C++ (ABI incompatibles).

On peut toutefois preciser que sous Windows, un systeme d'exception (Structured Exception Handling, ou SEH) est integre au sein meme du systeme. Certains compilateurs l'exploitent, y compris en langage C (au moyen de mots cles specifiques), ce qui permet a du code C d'etre traverse sans probleme par des exceptions lancees depuis un code C++, si celles-ci ont ete emises 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 compile. Consultez la documentation de votre compilateur pour plus de details.


rechercher
precedent    sommaire    suivant    telechargermiroir

Consultez les autres F.A.Q's


Valid XHTML 1.1!Valid 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.