FAQ C++Consultez toutes les FAQ
Nombre d'auteurs : 34, nombre de questions : 368, dernière mise à jour : 14 novembre 2021 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.
Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.
Sur ce, nous vous souhaitons une bonne lecture.
- 10.1. Les constructeurs (17)
- 10.2. Les destructeurs (12)
- 10.3. La surcharge d'opérateurs (18)
- 10.4. Les fonctions membres virtuelles (6)
- 10.5. Les amis (friend) (7)
- 10.6. Les données et fonctions membres statiques (10)
- 10.7. Sémantique de copie (7)
- 10.8. référencce (0)
- Qu'est-ce qu'une classe ?
- Qu'est-ce qu'une classe abstraite ?
- Quand est-ce qu'une classe a une sémantique de valeur ?
- Quand est-ce qu'une classe a une sémantique d'entité ?
- Quelle forme canonique adopter en fonction de la sémantique de la classe ?
- Qu'est-ce que la forme canonique orthodoxe de Coplien ?
- Que conclure de la forme canonique orthodoxe de Coplien ?
- Qu'est-ce que la règle des grands trois (Big rule of three) ?
- Comment structurer ma classe en un fichier .h et un fichier .cpp ?
- Comment créer deux classes qui font référence l'une à l'autre ?
- Comment puis-je empêcher les autres programmeurs de violer l'encapsulation en accédant aux membres privés de mes classes ?
- Comment dériver une classe à partir d'une autre ?
- Comment faire pour empêcher de créer plus d'une instance d'une classe ?
- Qu'est-ce « this » ?
- Que signifient public, private et protected ?
- Quelle est la différence entre class et struct ?
- Que signifie objet.fonction1().fonction2() ? (chaînage de fonctions)
- Comment effectuer la copie d'objets polymorphes ?
- Pourquoi certaines fonctions membres possèdent le mot clé const après leur nom ?
- Qu'est-ce que le masquage de fonction (name hiding) ?
- Pourquoi ne peut-on pas passer une fonction membre là où on attend un pointeur de fonction ?
- La fonction de ma classe entre en conflit avec une fonction standard, que faire ?
D'une façon très simple et en faisant le parallèle avec le C, on peut dire qu'une classe est une structure à laquelle on ajoute des fonctions permettant de gérer cette structure.
Mais outre l'ajout de ces fonctions, il existe deux grandes nouveautés par rapport aux structures du C :
- d'une part, les membres de classe, qu'ils soient des variables ou des fonctions, peuvent être privés c'est-à-dire inaccessibles en dehors de la classe (par opposition aux membres publics d'une structure C où tous ses membres sont accessibles).
- d'autre part, une classe peut être dérivée. La classe dérivée hérite alors de toutes les propriétés et fonctions de la classe mère. Une classe peut d'ailleurs hériter de plusieurs classes simultanément.
Une classe se déclare via le mot-clé class suivi du nom de la classe, d'un bloc (accolades ouvrante et fermante) et d'un point virgule (ne pas l'oublier !). Les membres de la classe (variables ou fonctions) doivent être déclarés à l'intérieur de ce bloc, à la manière des structures en C.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | class MaClasse { // Déclaration de toutes les variables ou fonctions membres // constitutives de la classe int a; // variable membre void b(); // fonction membre }; |
Une classe abstraite est une classe qui possède au moins une fonction membre virtuelle pure (lire Qu'est-ce qu'une fonction virtuelle pure ?). Cette fonction devant être supplantée, ce type de classe ne peut pas être instancié, et est donc destiné à être dérivé pour être spécialisé. La ou les classes filles doivent supplanter l'ensemble des fonctions virtuelles pures de leurs parents. On dit alors que les classes filles concrétisent la classe abstraite.
Code c++ : | Sélectionner tout |
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 | class Bienvenue // classe abstraite { public: // le "= 0" à la fin indique que c'est // une fonction virtuelle pure virtual void Message() = 0; }; class BienvenueEnFrancais : public Bienvenue { public: void Message() { std::cout << "Bienvenue !\n"; } }; class BienvenueEnAnglais : public Bienvenue { public: void Message() { std::cout << "Welcome !\n"; } }; |
On dit d'une classe qu'elle a une sémantique de valeur si deux objets situés à des adresses différentes, mais au contenu identique, sont considérés égaux.
Par exemple, une classe modélisant une somme d'argent a une sémantique de valeur. Une somme de 100 € est toujours une somme de 100 €. On peut ajouter, soustraire, multiplier différentes sommes d'argent. Deux sommes de 100 € sont identiques même si l'une est sous forme d'espèces et l'autre sous forme de chèque.
On voit qu'il peut être pertinent :
- de redéfinir des opérateurs arithmétiques (+, -, *, /) ;
- d'avoir un opérateur d'affectation (=, constructeur par copie ?) ;
- de redéfinir des opérateurs de comparaison (==, <, etc.).
Inversement, une classe à sémantique de valeur n'a pas beaucoup de sens pour servir de classe de base à un héritage. On ne trouvera donc en général pas de fonction virtuelle dans une classe à sémantique de valeur.
À l'inverse des classes à sémantique de valeur, une classe a une sémantique d'entité si toutes les instances de cette classe sont nécessairement deux à deux distinctes, même si tous les champs de ces instances sont égaux. Elle modélise un concept d'identité : chaque objet représente un individu unique.
Une classe modélisant un compte a une sémantique d'entité. Deux comptes sont distincts même s'ils ont la même somme d'argent. Cela n'a pas de sens d'ajouter des comptes (on peut vider un compte pour le verser dans un autre, mais ce n'est pas un ajout). En revanche, on peut avoir des comptes courants, des comptes d'épargnes, des comptes titres, etc.
On voit qu'une classe à sémantique d'entité peut servir de base à un héritage. Mais, une classe à sémantique d'entité :
- ne redéfinit pas les opérateurs arithmétiques (+, -, /, *) ;
- n'a pas d'opérateur d'affectation (=, constructeur par copie) ;
- ne redéfinit pas les opérateurs de comparaison (==, <, etc.).
Si on veut créer une copie d'un objet à sémantique d'entité, on s'appuie sur une méthode Clone spécifique retournant un nouvel objet dans un état semblable.
Lorsqu'une classe doit gérer de la mémoire, il est d'usage de la mettre sous une forme que l'on appelle forme canonique. Mais il existe plusieurs variantes de la forme canonique, selon que la classe soit copiable, modifiable, swappable…
Classe non-copiable, non-assignable, mais swappable :
- constructeur ;
- constructeur par copie : privé, non-défini ;
- operator= : privé, non-défini ;
- swap() + spécialisation de std::swap ;
- destructeur.
Classe copiable, mais immuable (non-assignable, non-swappable)
- constructeur ;
- constructeur par copie ;
- operator= : privé, non-défini ;
- destructeur.
Classe pleinement copiable, assignable, swappable :
- constructeur ;
- constructeur par copie ;
- operator= : utilise idiome copy-and-swap ;
- swap() + spécialisation de std::swap ;
- destructeur.
De plus, le destructeur n'a pas forcément à être virtuel, cela dépend des cas. Si l'héritage est public et que la classe de base va servir en tant qu'interface alors le destructeur doit être virtuel. Par contre si l'héritage est protégé/privé et qu'il sert à réutiliser du code sans notion d'interface et de polymorphisme, alors le destructeur de la classe de base doit être protégé et non-virtuel.
Enfin pour les constructeurs, il est souvent préconisé de les faire protégés pour toutes les classes non-terminales, seules les classes terminales doivent être instanciables.
La forme canonique orthodoxe de Coplien a été définie par James Coplien dans son livre « Advanced C++ Programming Styles and Idioms » paru en 1991. Elle précise que toute classe à sémantique de valeur doit proposer ces quatre comportements de base dans son interface publique :
- Le constructeur par défaut CMyClass() ;
- Le constructeur de copie CMyClass(CMyClass const &) ;
- L'opérateur d'affectation : CMyClass&operator=(CMyClass const &) ;
- Le destructeur : ~CMyClass().
Le compilateur est capable de fournir une implémentation triviale pour ces fonctions si vous ne lui donnez pas de raison de ne pas le faire :
- Le compilateur ne fournira pas d'implémentation triviale du constructeur par défaut si vous fournissez un constructeur prenant ou non des arguments.
- Le compilateur ne fournira pas d'implémentation triviale pour le constructeur de copie, l'opérateur d'affectation et le destructeur si vous en fournissez une implémentation personnelle.
Lorsque vous définissez le comportement du constructeur par copie, de l' opérateur d'affectation ou du destructeur, vous devriez suivre la règle des trois (big rule of three).
- Si une des quatre méthodes ci-dessus a été définie de façon non triviale, alors il est fortement probable que les trois autres doivent être définies.
- Si une classe doit servir de base pour une utilisation polymorphe, alors le destructeur doit être déclaré comme virtuel. À noter qu'il est fort probable que la classe soit alors non copiable.
- La sémantique d'une classe va imposer la politique de définition des méthodes ( Quelle forme canonique adopter en fonction de la sémantique de la classe ?).
- Si la définition implicite convient, compte tenu des éléments ci-dessus, alors il n'est pas nécessaire de définir explicitement ces méthodes. Et les définir peut avoir des impacts négatifs : il existe des outils d'analyse de code (et peut-être des futures évolutions du langage) qui vérifient justement que l'on a respecté ces règles. Définir une fonction ne faisant rien peut alors mettre en échec ces outils.
La forme canonique orthodoxe de Coplien nous indique qu'il y a quatre fonctions de base que toute classe ayant sémantique de valeur doit fournir.
Cependant, il est tout à fait possible qu'une classe ayant sémantique de valeur utilise en interne de la mémoire allouée de manière dynamique, ce qui ne manque pas d'occasionner un certain nombre de problèmes.
La règle des grands trois apporte une correction à la forme canonique orthodoxe de Coplien qui tend à éviter les problèmes que nous pourrions rencontrer et s'énonce ainsi :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | class MyValueClass { public: ~MyValueClass() {delete [] ptr;} private: UnType * ptr; }; |
La même adresse sera donc utilisée par deux objets différents, ce qui ne manquera pas d'occasionner des tentatives de double libération de la mémoire ou des tentatives d'accès à une mémoire déjà libérée.
Vous devrez donc également vous assurer que :
- Le constructeur de copie fasse une "copie en profondeur" du pointeur ;
- L'opérateur d'affectation libère correctement la mémoire allouée au pointeur d'origine avant de lui affecter le résultat de la copie en profondeur.
Votre classe devrait donc ressembler à
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class MyValueClass { public: MyValueClass( const MyValueClass & rhs) { /* copie en profondeur de rhs.ptr dans ptr */ } MyValueClass & operator = ( const MyValueClass & rhs) { /* copie en profondeur de rhs.ptr dans ptr sans oublier * de libérer correctement la mémoire allouée à ptr */ } ~MyValueClass() {delete [] ptr;} private: UnType * ptr; }; |
- l'idiome copy-and-swap est généralement conseillé pour implémenter l' opérateur d'affectation.
- Avec l'apparition des pointeurs intelligents et dans une approche RAII, cette règle a évolué vers la règle de deux puis, avec de C++11 et sa sémantique de mouvement, vers la règle de un.
Une classe est déclarée dans un fichier header (extension .h, .hpp. ou encore .hxx) dont l'inclusion multiple est protégée grâce à des directives du préprocesseur :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | #ifndef MACLASSE_H #define MACLASSE_H class MaClasse { public: void Fonction(); }; #endif |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | #include "maclasse.h" void MaClasse::Fonction() { // implémentation de la fonction } |
Certains seront parfois tentés d'inclure un fichier .cpp : c'est une erreur et cela ne doit jamais être fait (il en résulterait plusieurs corps pour une même fonction, par exemple).
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | #include "autreclasse.h" #include "maclasse.h" void AutreClasse::Fonction() { MaClasse M; M.Fonction(); } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #ifndef MACLASSE_H #define MACLASSE_H class MaClasse { public: void FonctionInline() { // Placée dans la déclaration de la classe // cette fonction est considérée en ligne sans // que le mot clé inline ne figure } }; // Placée en dehors de la déclaration de la classe, le mot clé inline // est indispensable pour ne pas provoquer des définitions multiples // de la fonction et donc des erreurs à l'édition de liens inline void FonctionInline2() { } #endif |
Il arrive souvent qu'une classe A contienne un attribut (ou un argument de fonction) de type classe B et que la classe B contienne elle aussi un attribut (ou un argument de fonction) de type classe A. Mais si l'on inclut A.h dans B.h et vice-versa, on se retrouve avec un problème d'inclusions cycliques.
La solution à ce problème est d'utiliser des déclarations anticipées (forward declaration). Au lieu d'inclure l'en-tête définissant une classe, on va simplement déclarer celle-ci pour indiquer au compilateur qu'elle existe. Cela marche car tant qu'on n'utilise qu'un pointeur ou une référence, le compilateur n'a pas besoin de connaître en détail le contenu de la classe. Il a juste besoin de savoir qu'elle existe. Par contre au moment d'utiliser celle-ci (appel d'une fonction membre par exemple) il faudra bien avoir inclus son en-tête, mais ce sera fait dans le .cpp et non plus dans le .h, ce qui élimine le problème d'inclusion cyclique.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | class B; class A { B* PtrB; }; |
Code c++ : | Sélectionner tout |
1 2 3 4 | #include "A.h" #include "B.h" // ... |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | #include "A.h" class B { A a; }; |
Code c++ : | Sélectionner tout |
1 2 3 | #include "B.h" // ... |
Le jeu n'en vaut pas la chandelle, l'encapsulation est faite pour le code, pas pour les gens.
Ce n'est pas violer l'encapsulation pour un programmeur que de voir les parties privées et/ou protégées de vos classes, tant qu'il n'écrit pas de code qui dépende d'une façon ou d'une autre de ce qu'il voit. En d'autres termes, l'encapsulation n'empêche pas les gens de découvrir comment est constituée une classe, cela empêche que le code que l'on écrit ne soit dépendant de l'intérieur de la classe. La société qui vous emploie ne doit pas payer un « contrat de maintenance » pour entretenir la matière grise qui se trouve entre vos deux oreilles, mais elle doit payer pour entretenir le code qui sort de vos doigts. Ce que vous savez en tant que personne n'augmente pas le coût de maintenance, partant du principe que le code que vous écrivez dépend de l'interface plus que de l'implémentation.
D'un autre coté, ce n'est rarement, voire jamais un problème. Je ne connais aucun programmeur qui ait intentionnellement essayé d'accéder aux parties privées d'une classe. « Mon avis dans un tel cas de figure serait de changer le programmeur et non le code » (James Kanze, avec son autorisation)
Pour dériver une classe à partir d'une autre, il suffit de faire suivre la déclaration de la classe dérivée de : suivi d'un modificateur d'accès et du nom de la classe mère.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | class mere { // ... } class fille : public mere { // ... } |
Si l'héritage est public, les membres de la classe mère garderont leur accès, à savoir les membres publics restent publics et les protégés restent protégés.
Si l'héritage est protected, les membres publics de la classe mère deviendront protégés dans la classe fille, et les protégés resteront tels quels.
Si l'héritage est private, tous les membres de la classe mère, qu'ils aient été publics ou protégés deviennent privés.
Les membres privés de la classe mère sont inaccessibles depuis la classe fille, quelque soit le type d'héritage.
Pour limiter le nombre d'instances d'une classe, on utilise le design pattern du singleton. Il permet de contrôler le nombre d'instances d'une classe (en général une seule). Voici un exemple typique d'implémentation en C++ :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // fichier Singleton.h #ifndef SINGLETON_H #define SINGLETON_H class Singleton { public : // Fonction permettant de récupérer l'instance unique de notre classe static Singleton & GetInstance(); private : // On empêche la création directe d'objets Singleton en mettant son constructeur par défaut privé // Ainsi on oblige l'utilisateur à passer par GetInstance Singleton() {} // On empêche la recopie d'objets Singleton en mettant le constructeur par copie // et l'opérateur d'affectation en privé // On peut même ne pas les implémenter, c'est encore mieux Singleton( const Singleton & ); Singleton & operator =( const Singleton & ); }; #endif |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | // fichier Singleton.cpp #include "Singleton.h" Singleton & Singleton::GetInstance() { static Singleton instance; // instance unique cachée dans la fonction. Ne pas oublier le static ! return instance; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | // exemple d'utilisation #include "Singleton.h" int main() { // Essai : on tente de créer un Singleton Singleton erreur; // Ne compile pas - le constructeur par défaut n'est pas accessible // Récupération de l'instance unique de la classe Singleton & s = Singleton::GetInstance(); } |
On peut également modifier le code pour le rendre thread-safe en ajoutant une protection de type verrou dans GetInstance() si l'on fait du multithreading.
this est un pointeur créé par défaut et qui désigne l'objet lui-même. À noter que this est un pointeur non modifiable, l'adresse pointée ne peut être changée (ce qui est d'ailleurs parfaitement logique).
Un membre déclaré public dans une classe peut être accédé par toutes les autres classes et fonctions.
Un membre déclaré protected dans une classe ne peut être accédé que par les autres membres de cette même classe ainsi que par les membres des classes dérivées.
Un membre déclaré private dans une classe ne peut être accédé que par les autres membres de cette même classe.
Ces mots-clés permettent également de modifier la visibilité des membres dans la classe dérivée lors d'un héritage :
Héritage | ||||
Accès aux données | public | protected | private | |
public | public | protected | private | |
protected | protected | protected | private | |
private | interdit | interdit | interdit |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class A { public : int x; protected : int y; private : int z; }; class B : private A { // x et y sont ici en accès privé, et z est inaccessible } |
Contrairement à ce que l'on pourrait penser, les classes (class) et les structures (struct) sont équivalentes en C++.
Seules trois différences mineures existent :
- La visibilité par défaut est public pour les structures, private pour les classes.
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19struct A { int a; private : int b; }; class B { int b; public : int a; }; // Ici, A et B sont équivalents
- Le mode d'héritage par défaut est public pour les structures, private pour les classes.
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Base {}; // class ou struct, peu importe class A1 : public Base { }; struct A2 : Base { }; class B1 : Base { }; struct B2 : private Base { }; // Ici, A1 et A2 sont équivalents, ainsi que B1 et B2
- L'utilisation en tant que type template est interdite pour struct.
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9
10
11// OK template <class T> struct A { }; // ERREUR template <struct T> class B { };
À noter que la norme permet même d'effectuer une déclaration anticipée de classe via le mot-clé struct, et inversement. Cependant, certains compilateurs ne l'acceptent pas.
Fichier.h
Code c++ : | Sélectionner tout |
1 2 3 | struct A; // déclaration anticipée en tant que struct A * Exemple(); |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | class A // déclaration en tant que class { }; A * Exemple() { return new A; } |
C'est l'enchaînement des appels de ces fonctions membres, c'est pourquoi cela s'appelle le chaînage des fonctions.
La première chose qui est exécutée est objet.methode1(). Cela renvoie un objet ou une référence sur un objet (par ex. methode1() peut se terminer en renvoyant *this, ou n'importe quel autre objet). Appelons cet objet retourné « ObjetB ». Ensuite, ObjetB devient l'objet auquel est appliqué methode2().
L'utilisation la plus courante du chaînage de fonctions est l'injection / extraction vers les flux standards.
Code c++ : | Sélectionner tout |
cout << x << y ;
Code c++ : | Sélectionner tout |
cout << x
Parfois, lorsqu'on manipule des objets polymorphes (i.e. des classes dérivées via un pointeur sur leur classe de base), on voudrait connaître leur type réel, par exemple pour les copier. Malheureusement, conceptuellement ce n'est souvent pas la meilleure solution, et c'est parfois lourd à gérer. Le moyen le plus efficace de procéder à une copie d'objets polymorphes est sans doute d'utiliser le design pattern Clone :
Code c++ : | Sélectionner tout |
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 | // Définition de notre classe de base struct Base { virtual ~Base() {} virtual Base* Clone() const = 0; }; // Une classe dérivée de Base struct Derivee1 : public Base { virtual Base* Clone() const { return new Derivee1(*this); } }; // Une autre classe dérivée de Base struct Derivee2 : public Base { virtual Base* Clone() const { return new Derivee2(*this); } }; // Utilisation Base* Obj1 = new Derivee1; Base* Obj2 = new Derivee2; Base* Copy1 = Obj1->Clone(); // Copy1 pointe sur un objet de type Derivee1 Base* Copy2 = Obj2->Clone(); // Copy2 pointe sur un objet de type Derivee2 delete Obj1; delete Obj2; delete Copy1; delete Copy2; |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | struct Derivee1 : public Base { virtual Derivee1* Clone() const { return new Derivee1(*this); } }; |
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.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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" } |
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.
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 :
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | #include <iostream> #include <string> struct Base { void F(int); }; struct Derivee : Base { void F(std::string); }; |
Code C++ : | Sélectionner tout |
1 2 3 | 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 :
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 | struct Derivee : Base { using Base::F; void F(std::string s); }; |
Code C++ : | Sélectionner tout |
1 2 3 | 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 :
Code C++ : | Sélectionner tout |
1 2 | Derivee d; d.Base::F(5); // Ok : appelle Base::F |
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) :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 ! |
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 ». À 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 :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 ); |
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 ::.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | 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() } |
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes 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 © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et 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.