Salut,
Si tu veux être complet sur ce qui peut se passer lorsque tu dois fournir un paramètre à une fonction, il faut savoir qu'il peut se passer plein de choses différentes, qui se subdivisent ainsi:
- Tu peux passer un simple alias d'une variable: la variable utilisée au sein de la fonction est vraiment celle qui est utilisée comme argument, éventuellement connue sous un autre nom
- Tu peux observer la création d'une variable
- Par copie
- par conversion, celle-ci pouvant survenir
- de manière implicite (sans que l'utilisateur ne doive préciser qu'il souhaite la conversion)
- de manière explicite (l'utilisateur doit préciser qu'il souhaite la conversion, si c'est le cas
- le compilateur sort sur une erreur
Pour pouvoir déterminer précisément ce qui va se passer, il va falloir prendre plusieurs contextes en compte.
Je vais d'abord en dresser la liste, puis j'expliquerai chacun des concepts, et enfin, j'en viendrai aux explications
- le fait que la variable qui sert d'argument existe (aie été déclarée dans la fonction appelante) avant l'appel de la fonction en ayant le bon type (ou un type dérivé) ou non
- Le fait que l'argument soit fourni sous forme de référence ou sous forme d'objet
- Le fait que la norme interdit les variables anonymes(temporaires) non constantes
- Les constructeurs existant pour le type de l'argument passé
- Le fait que l'un ou l'autre constructeur soit déclaré explicit.
La première question à se poser consiste donc à déterminer si la variable qui sert d'argument existe déjà dans la fonction appelante.
Cela signifie que c'est soit un argument qui a été passé à la fonction appelante, soit que la variable a été déclarée avant l'appel de la fonction, et bien sûr, le fait que la variable soit du bon type (c'est à dire, soit exactement le bon type, soit une classe dérivée du type attendu).
La seconde question à se poser est la manière dont l'argument est passé à la fonction, et c'est le prototype de la fonction qui nous permet d'y répondre:
nous avons un passage par objet si le prototype est du genre de
type_de_retour laFonctionParObjet(type_argument argument);
et nous avons un passage par référence si le prototype est du genre de
type_de_retour laFonctionParReference(type_argument& argument);
L'interdiction par la norme de créer une variable anonymes (temporaires) non constantes va influer directement sur notre choix de déclarer l'argument const ou non.
Si la variable n'existe pas (ou risque de/peut ne pas exister) en ayant le bon type (ou un type qui dérive du type attendu) avant l'appel de la fonction, il y aura d'office création d'une variable temporaire lors de l'appel à la fonction.
Dans ce cas, l'argument devra être déclaré constant, et le prototype devra donc prendre la forme de
type_de_retour laFonctionParObjetConstant(const type_argument argument);
ou de
type_de_retour laFonctionParReferenceConstante( const type_argument& argument);
selon le cas
On l'oublie souvent, mais un constructeur prenant un argument peut servir d'opérateur de conversion, servant à créer un élément du type de la classe dont on défini le constructeur au départ du type de l'argument que reçoit ce constructeur, et ce, quel que soit le type de l'argument (à l'exception de la classe dont on définit le constructeur, ca va de soi... vu qu'il prend alors le nom de constructeur par copie ).
Par défaut, la conversion est implicite, ce qui signifie que l'utilisateur n'a aucun besoin de la demander pour qu'elle ait lieu.
Ainsi, si tu as deux classes et une fonction sous une forme proche de
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class A
{
public:
/* constructeur de la classe A, sans argument, et ne faisant rien
*/
A(){}
};
class B
{
public:
/* Constructeur de B prenant un A en argument,
* sans s'inquiéter de savoir s'il est constant ou non, ou si c'est une
* référence ou non
*/
};
/* ici, je veux montrer la conversion, je sais donc que j'aurai une variable
* temporaire, et il faut donc que l'argument soit constant
* par contre, le fait qu'il s'agisse d'un objet ou d'une référence ne jouera pas
* dans la démonstration ;)
*/
void laFonction(const B b)
{
/*ce qu'il faut faire avec b*/
} |
on pourrait très bien avoir le code suivant
1 2 3 4 5 6
| int main()
{
A monA;
laFonction(monA);//monA est implicitement converti en B au moment de l'appel
return 0;
} |
et cela fonctionnera sans aucun problème, grâce à la conversion implicite.
On s'est rendu compte à l'usage, mais alors qu'il y avait déjà énormément de codes qui utilisaient cette possibilité, que ce genre de comportement peut représenter un danger dans certaines circonstances particulières...
Il a donc été décidé de rajouter le mot clé explicit, à mettre devant le constructeur, de manière à permettre d'indiquer au compilateur qu'il ne doit effectuer la conversion que si l'utilisateur le demande explicitement.
Ainsi, avec une classe C et une fonction laFonctionC définies sous la forme de
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
class C
{
public:
/* Constructeur de C prenant un A en argument,
* sans s'inquiéter de savoir s'il est constant ou non, ou si c'est une
* référence ou non
*/
explicit C(A a){}
};
/* ici, je veux montrer la conversion, je sais donc que j'aurai une variable
* temporaire, et il faut donc que l'argument soit constant
* par contre, le fait qu'il s'agisse d'un objet ou d'une référence ne jouera pas
* dans la démonstration ;)
*/
void laFonction(const C c)
{
/*ce qu'il faut faire avec c*/
} |
le code suivant sera refusé par le compilateur (parce que l'utilisateur ne demande pas explicitement de convertir le A en C)
1 2 3 4 5 6
| int main()
{
A monA;
laFonctionC(monA);//La conversion échoue car l'utilisateur ne dit pas
//explicitement qu'il veut l'effectuer
} |
mais le code suivant fonctionnera, car l'utilisateur demande explicitement la conversion:
1 2 3 4 5 6
| int main()
{
A monA;
laFonctionC(C(monA));//La conversion échoue car l'utilisateur ne dit pas
//explicitement qu'il veut l'effectuer
} |
Au final, tu te rend compte que la réponse complète sur ce qui se passe quand tu passe un argument à une variable va être bien plus compliquée que de savoir s'il s'agit d'une copie ou non...
Voici sans doute la meilleure manière de la présenter:
- Si la variable du bon type ou d'un type dérivé existe
- Si passage par référence : pas de construction: utilisation d'un alias
- Si passage par objet : construction par copie
- Si la variable du bon type ou d'un type dérivé n'existe pas, par référence comme par objet
- Si argument non constant : echec
- Si argument constant :
- Si pas de constructeur adéquat pour l'argument :echec
- Si constructeur adéquat
- Si constructeur explicit
- Si conversion demandée : construction par conversion
- Si conversion non demandée : échec
- Si pas constructeur explicit : construction par conversion
Pfffiouuu... Voilà encore un post digne des annales ...
Et encore, je n'ai pas parler des pointeurs...
Sache que le pointeurs suivra exactement les mêmes règles que s'il s'agissait d'une variable tout à fait normale, simplement, le fait de prendre l'adresse d'une variable existante provoque la création du pointeur (et un pointeur est toujours un type primitif ayant un nombre de bits suffisant pour représenter au minimum toutes les adresses mémoires disponible) mais que l'objet pointé lui sera d'office l'équivalent d'un alias de variable
[EDIT]Au fait, et pour m'assurer que tu comprenne bien le sens de la phrase du bon type ou d'un type dérivé (et similaire) utilisé dans tout ce texte, je parle vraiment de la relation d'héritage entre une classe de base et ses classes dérivées.
Ainsi, si tu as un arbre d'héritage du genre de
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class GrandMere
{
/*...*/
};
class Base : public GrandMere
{
/*...*/
};
class Derivee : public Base
{
};
class Derivee2 : public Base
{
}; |
et que ta fonction attend un élément de type Base (sous forme d'objet ou de référence), les objets de type Base, Derivee et Derivee2 (ainsi que les types qui héritent de l'une de ces trois classes) seront considérés comme étant du bon type.
Par contre, les objets de type GrandMere ou qui héritent de GrandMere sans hériter de Base seront considérés comme étant d'un mauvais type
Partager