Bonjour tout le monde,
Je voudrais juste savoir si ceci :
vous fais penser à l'appel d'un constructeur par copie ?Code:m_TC.Ajouter(CCercle(CC.Champs(1),atof(CC.Champs(2))));
Merci d'avance pour votre aide.
beegees
Version imprimable
Bonjour tout le monde,
Je voudrais juste savoir si ceci :
vous fais penser à l'appel d'un constructeur par copie ?Code:m_TC.Ajouter(CCercle(CC.Champs(1),atof(CC.Champs(2))));
Merci d'avance pour votre aide.
beegees
Ca ne marchera pas, car l'objet créé est un temporaire, et on ne peut pas passer par référence un temporaire. Donc plutôt :
Maintenant, rien n'empêche le code dans ajouter de faire des copies. Si tu veux vraiment tester ça, pourquoi ne pas interdire la copie (constructeur par recopie privé) et voir ce que ça donne ?Code:void TaClasse::Ajouter( CCercle const & cercle );
Tout dépend de ce qu'est CCercle !
Mais au vu de son nom il doit s'agir d'une classe non ?
Donc au vue de la ligne fournie tu crées ton objet CCercle "à la volée". Autrement dis c'est un temporaire.
Mais pour répondre à la question initiale montres nous le prototype de Ajouter
Un temporaire est une variable qui n'a pas de nom.
C'est à dire que c'est une variable qui désigne quelque chose, mais que l'on identifie pas par un nom.
Salut,
Je vais essayer d'être clair :mrgreen:
Dans cet exemple précis, tu passes ton objet par référence. Donc peu importe l'endroit où il est créé, lorsque tu passes ton objet en paramètre, tu obtiens l'objet lui-même, pas une copie.
Donc il n'y a aucune raison pour qu'un quelconque constructeur soit appelé en plus.
Dans ton exemple, cela fait que le seul constructeur appelé sera:
Et il ne le sera qu'une fois!Code:CCercle::CCercle(const char* param1, float param2);
D'ailleurs, un exemple en code:
Et quand on regarde le log, on voit qu'aucun constructeur par copie n'a été appelé à aucun moment.Code:
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 #include <iostream> using namespace std; class A { public: int i; //Constructeur par défaut A(int i) : i (i) {cout << "constructeur avec entier appele!" << endl;} //par Copie A(const A& Source) : i(Source.i) { cout << "Constructeur par copie appele!" << endl;} //La fonction ajouter qui t'intéresse: void ajouter(const A& item) { cout << "A::ajouter(const A& item)" << endl;} }; int main() { //On crée d'abord la classe pour faire le test. //initialisation avec entier A objet(8); //Saut de ligne cout << endl; //Puis on appelle ta fonction: objet.ajouter(A(2)); return 0; }
Et si le prototype avait été bool Ajouter(CRectangle Modele) ?
Il y a deux cas possibles:
- Si tu crées l'objet dans ton appel à la fonction, comme tu le fais:
Alors l'objet est créé et passé tel quel à ta fonction, en effet il n'est pas utilisé en dehors alors pourquoi en faire une copie?Code:m_TC.Ajouter(CCercle(CC.Champs(1),atof(CC.Champs(2)));
- Si tu crées l'objet avant:
Alors là, par contre, on a besoin de passer une copie à la fonction Ajouter(), et donc le constructeur par copie de CCercle sera appelé ;)Code:
1
2
3 CCercle MonCercle(CC.Champs(1),atof(CC.Champs(2)); m_TC.Ajouter(MonCercle);
D'ailleurs, pour ces deux derniers cas (qui ne concernent pas ton exemple), le code pour tester n'est modifié que très légèrement (par rapport à celui que j'ai donné plus haut).
Voilà! :)
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:
Pour pouvoir déterminer précisément ce qui va se passer, il va falloir prendre plusieurs contextes en compte.
- 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
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
et nous avons un passage par référence si le prototype est du genre deCode:type_de_retour laFonctionParObjet(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.Code:type_de_retour laFonctionParReference(type_argument& argument);
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
ou deCode:type_de_retour laFonctionParObjetConstant(const type_argument argument);
selon le casCode:type_de_retour laFonctionParReferenceConstante( const type_argument& argument);
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
on pourrait très bien avoir le code suivantCode:
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*/ }
et cela fonctionnera sans aucun problème, grâce à la conversion implicite.Code:
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; }
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 dele code suivant sera refusé par le compilateur (parce que l'utilisateur ne demande pas explicitement de convertir le A en C)Code:
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*/ }
mais le code suivant fonctionnera, car l'utilisateur demande explicitement la conversion:Code:
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 }
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...Code:
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 }
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 :P...
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
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.Code:
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 { };
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 ;)
temporaire = objet temporaire.
Une expression n'est jamais un temporaire (une expression n'est pas un objet). Mais l'évaluation d'une expression produit un temporaire : on dit alors que l'expression désigne un temporaire (sous entendu lors de son évaluation).
Une variable a une déclaration, donc un nom. Le terme variable n'est pas à prendre au sens de ce qui peut varier : la valeur d'un objet temporaire peut varier, mais ne n'est pas une variable; le valeur d'une constante déclarée avec le type "const int" ne peut pas varier, mais c'est une variable.
Une expression désigne un temporaire si c'est une r-valeur de type classe. Principalement, ce sont (Classe étant un type de classe) :
- l'appel d'une fonction ne renvoyant pas une référence
- la construction d'un temporaire : Classe (liste-d'arguments)
- ou static_cast<Classe>(expr) ou Classe(expr) ou (Classe)expr
(ces trois expressions sont équivalentes)
Tu as tout à fait raison. J'ai été trop vague. Je m'excuse envers le posteur original.
Merci des précisions corrector.
Salut Coyotte, désolé pour le retard de ma réponse.Citation:
Salut,
Avec moi, il vaut mieux :mouarf:Citation:
Je vais essayer d'être clair :mrgreen:
OK merci, ça s'est important de le rappeler.Citation:
Dans cet exemple précis, tu passes ton objet par référence. Donc peu importe l'endroit où il est créé, lorsque tu passes ton objet en paramètre, tu obtiens l'objet lui-même, pas une copie.
ça par contre je ne savais pas mais s'est logique vue que l'on ne crée pas un nouvel objet. Donc si je passe un objet par copie il appelle le constructeur ?Citation:
Donc il n'y a aucune raison pour qu'un quelconque constructeur soit appelé en plus.
Donc on appelle le constructeur uen seule fois lors de la création de l'objet de type CCercle alors ?Citation:
Dans ton exemple, cela fait que le seul constructeur appelé sera:
Et il ne le sera qu'une fois!Code:CCercle::CCercle(const char* param1, float param2);
Merci, je vais tester.Citation:
D'ailleurs, un exemple en code:
Code:
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 #include <iostream> using namespace std; class A { public: int i; //Constructeur par défaut A(int i) : i (i) {cout << "constructeur avec entier appele!" << endl;} //par Copie A(const A& Source) : i(Source.i) { cout << "Constructeur par copie appele!" << endl;} //La fonction ajouter qui t'intéresse: void ajouter(const A& item) { cout << "A::ajouter(const A& item)" << endl;} }; int main() { //On crée d'abord la classe pour faire le test. //initialisation avec entier A objet(8); //Saut de ligne cout << endl; //Puis on appelle ta fonction: objet.ajouter(A(2)); return 0; }
Bon si ça concerne pas mon cas, je vais pas trop les lire pour le moment afin de ne pas trop m'embrouiller.Citation:
Et quand on regarde le log, on voit qu'aucun constructeur par copie n'a été appelé à aucun moment.
Et si le prototype avait été bool Ajouter(CRectangle Modele) ?
Il y a deux cas possibles:
- Si tu crées l'objet dans ton appel à la fonction, comme tu le fais:
Alors l'objet est créé et passé tel quel à ta fonction, en effet il n'est pas utilisé en dehors alors pourquoi en faire une copie?Code:m_TC.Ajouter(CCercle(CC.Champs(1),atof(CC.Champs(2)));
- Si tu crées l'objet avant:
Alors là, par contre, on a besoin de passer une copie à la fonction Ajouter(), et donc le constructeur par copie de CCercle sera appelé ;)Code:
1
2
3 CCercle MonCercle(CC.Champs(1),atof(CC.Champs(2)); m_TC.Ajouter(MonCercle);
D'ailleurs, pour ces deux derniers cas (qui ne concernent pas ton exemple), le code pour tester n'est modifié que très légèrement (par rapport à celui que j'ai donné plus haut).
Merci encore à toi, à Koala et aux autres pour votre aide qui est hyper utile, j'ai fais un grand bon en avant depuis deux semaines, je me sents bien plus à l'aise en C++ depuis ses deux dernières semaines.Citation:
Voilà! :)
beegees
Bonjour Koala,
8O un super grand merci pour ta hyper géniale réponse.
Je dois t'avouer que je n'y comprends pas tout mais bon, je vais encore le relire deux ou trois fois et puis ça devrait aller.
Une chose que je sais, s'est que ta réponse a déjà sa place dans mon cours :king:
Saurais-tu me dire ce que tu entends pas alias ?Citation:
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
Comme je te l'ai dis, je vais le relire, s'est une réponse de haut niveau 8O et je dois le lire et le relire avant de bien comprendre.
Encore un grand :merci: pour ton aide.
beegees
Exactement ce que l'on entend par la définition classique du terme alias:
Il s'agit tout simplement de l'autre nom sous lequel est connu une chose ou une personne.
Ici, cela signifie tout simplement que, quand tu passe un argument sous la forme d'une référence, la variable identifiée dans la fonction appelée est exactement celle qui a servi d'argument dans la fonction appelante.
Au final, si tu apporte une modification quelconque dans la fonction appelée, cette modification persiste, après l'appel de la fonction, dans la fonction appelante.
simplement, je voulais faire comprendre que, si tu as une fonction du genre de
et que tu l'appelle sous la forme deCode:void fonction(UnType& monArg);
La variable qui s'appelait maVar dans la fonction appelante s'appelle monArg, dans la fonction.Code:fonction(maVar);