|
auteur :
Aurelien.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';
const Test & t2 = t1;
cout << t2.F() << '\n';
} |
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.
|
|
auteurs :
LFE, Aurelien.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:
int var1;
void f1() {};
static int var2;
static void f2() {};
};
int A::var2 = 0;
int main()
{
A a;
a.var1 = 1;
a.f1();
A::var2 = 1;
A::f2();
} |
|
|
auteur :
Loulou24 |
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");
d.F(5); |
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");
d.F(5); |
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 :
|
|
auteur :
Loulou24 |
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 )
{
return 0;
}
DWORD WINAPI MaClasse::Fonction2( void *Param )
{
return 0;
}
MaClasse Param;
CreateThread( NULL, 0, Fonction1, &Param, 0 NULL );
CreateThread( NULL, 0, &MaClasse::Fonction2, &Param, 0 NULL ); |
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.
|
|
auteur :
Loulou24 |
En C, il est possible de déclarer une fonction prenant un nombre de paramètre variables via "..." (c'est ce qu'on appelle l'ellipse).
L'exemple le plus connu est 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...
|
|
auteur :
Loulou24 |
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, '-'));
FonctionPasOptimisee(v);
FonctionOptimisee(v); |
Attention à ne pas oublier le const si le paramètre ne sera 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);
FonctionCorrecte(s1);
FonctionIncorrecte(s1 + s2);
FonctionCorrecte(s1 + s2);
FonctionIncorrecte("bonjour");
FonctionCorrecte("bonjour"); |
Cette remarque vaut également pour les pointeurs :
void FonctionIncorrecte(char* s)
{
}
void FonctionCorrecte(const char* s)
{
}
FonctionIncorrecte("bonjour");
FonctionCorrecte("bonjour"); |
|
|
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);
}
int MaClasse::abs(int x)
{
return ::abs(x);
} |
|
Consultez les autres F.A.Q's
 
Ce document issu de http://www.developpez.com est soumis à la licence
GNU FDL traduit en français
ici.
Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement.
Certaines parties de ce document sont sous copyright Marshall Cline
Les codes sources présentés sur cette page sont libres de droits, et vous pouvez les utiliser à votre convenance. Pour le reste, ce document constitue une oeuvre intellectuelle protégée par les droits d'auteurs.
Ce document issu de http://www.developpez.com est soumis à trois licences, en fonction des contributeurs :
- Les contributions de Clément Cunin et LFE sont soumises aux termes de la la licence GNU FDL traduite en français ici.
Permission vous est donnée de distribuer, modifier des copies des contributions de Clément Cunin et LFE tant que cette note apparaît clairement :
"Ce document issu de http://www.developpez.com est soumis à la licence GNU FDL
traduite en français ici.
Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement".
- Les contributions de Marshall Cline sont sous copyright
- Pour ce qui est des autres contributions : Copyright © 2005 Developpez LLC : Tous droits réservés Developpez LLC.
Aucune reproduction, ne peut en être faite sans l'autorisation expresse de Developpez LLC.
Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts.
Cette page est déposée à la SACD.
|