39 votes

Comportement du constructeur de copie explicite et utilisations pratiques

Une question récente m'a amené à m'interroger sur les constructeurs de copies explicites. Voici un exemple de code que j'ai essayé de compiler sous Visual Studio 2005 :

struct A
{
    A() {}
    explicit A(const A &) {}
};

// #1 > Compilation error (expected behavior)
A retByValue()
{
    return A();
}

// #2 > Compiles just fine, but why ?
void passByValue(A a)
{
}

int main()
{
    A a;
    A b(a); // #3 > explicit copy construction : OK (expected behavior)
    A c = a; // #4 > implicit copy construction : KO (expected behavior)

    // Added after multiple comments : not an error according to VS 2005.
    passByValue(a);
    return 0;
}

Maintenant, les questions :

  • Le n°2 est-il autorisé par la norme ? Si c'est le cas, quelle est la section pertinente décrivant cette situation ?
  • Connaissez-vous une utilisation pratique d'un constructeur de copie explicite ?

[EDIT] Je viens de trouver un lien amusant sur MSDN avec exactement la même situation, et un commentaire mystérieux de la fonction principale : "c est copié" (comme si c'était évident). Comme l'a souligné Oli Charlesworth : gcc ne compile pas ce code et je crois qu'il a raison de ne pas le faire.

0 votes

Je ne pense pas que les constructeurs de copies explicites soient une bonne idée. Où les avez-vous lus ?

1 votes

Cela semble être corrigé dans VC++2010 - il donne une erreur pour la ligne passByValue(a); .

0 votes

Si vous aviez un constructeur supplémentaire pour A qui n'est pas explicite, par exemple A(const char*) alors je pense que vous pouvez toujours appeler passByValue en passant quelque chose qui utilise ce constructeur alternatif, par exemple passByValue("foo") . Mais je ne suis pas assez confiant pour poster ceci comme une réponse réelle.

42voto

outis Points 39377

Je crois que les sections pertinentes de C++03 sont les suivantes §12.3.1 2 :

Un constructeur explicite construit des objets tout comme les constructeurs non explicites, mais ne le fait que lorsque la syntaxe d'initialisation directe ( 8.5 ) ou où les castings ( 5.2.9 , 5.4 ) sont explicitement utilisés. Un constructeur par défaut peut être un constructeur explicite ; un tel constructeur sera utilisé pour effectuer une initialisation par défaut ou une initialisation par valeur ( 8.5 ).

y § 8.5 12 :

L'initialisation qui se produit lors du passage des arguments, du retour de la fonction, du lancement d'une exception ( 15.1 ), le traitement d'une exception ( 15.3 ), et les listes d'initialisation entre accolades ( 8.5.1 ) est appelé initialisation de la copie et est équivalent à la forme

    T x = a;

L'initialisation qui se produit dans les nouvelles expressions ( 5.3.4 ), les expressions static_cast ( 5.2.9 ), les conversions de types en notation fonctionnelle ( 5.2.3 ), et les initialisateurs de base et de membres ( 12.6.2 ) est appelé initialisation directe et est équivalent à la forme

    T x(a);

Appel à passByValue(a) implique une initialisation par copie, et non une initialisation directe, et devrait donc être une erreur, conformément à C++03 § 12.3.1 2.

2 votes

Bien que les réponses de MSalters et d'Oli soient correctes, je vais accepter celle-ci car elle montre clairement que VS a tort. Merci !

5voto

MSalters Points 74024

La définition de passByValue est OK, car il n'y a pas de déclaration qui copie un objet A. Dans la définition de retByValue il y a bien sûr une instruction de retour qui copie un objet A.

1 votes

Je ne suis pas sûr de comprendre votre réponse. Juste pour être clair : j'ai gardé le code au minimum, mais c'est ok pour le compilateur d'appeler passByValue(A()) depuis le principal, ce qui, je crois, nécessite une copie implicite.

0 votes

D'après ce que j'ai compris, le a n'est jamais utilisé à l'intérieur de passByValue() Le compilateur l'ignore donc.

0 votes

@djeidot : Je crois que l'appel de passByValue nécessite toujours que le constructeur (et le destructeur) de la copie soit invoqué. Le compilateur est libre d'émettre ces appels dans certaines situations pour les valeurs de retour (bien que cela puisse changer la signification d'un programme pour les constructeurs de copie non triviaux !), mais je ne suis pas au courant d'autres endroits avec cette liberté.

3voto

CashCow Points 18388

Une utilisation pratique, avant C++11, de l'explicitation d'un constructeur de copie est le cas où il fait partie de l'opération consistant à rendre une classe non copiable.

Le danger est que, même si vous déclarez le constructeur de copie privé et que vous ne l'implémentez pas, si vous en copiez accidentellement un, soit dans un ami, soit dans la classe elle-même, le compilateur ne le détectera pas et vous n'obtiendrez qu'une erreur de lien difficile à trouver.

Le fait de le rendre explicite réduit également les risques, car le compilateur peut très bien détecter votre copie involontaire et indiquer la ligne où vous la faites.

En C++11 (et 14), il n'est pas nécessaire de le faire lorsque l'on utilise la fonction =delete car vous obtiendriez une erreur de compilation même en copiant dans la classe elle-même ou dans un ami.

2voto

Oli Charlesworth Points 148744

Pour développer la réponse de @MSalters (qui est correcte), si vous deviez ajouter passByValue(a); à votre main() le compilateur serait devrait s'en plaindre.

Les constructeurs de copie explicite servent à empêcher exactement cela, c'est-à-dire à empêcher la copie implicite de ressources dans les appels de fonction et ainsi de suite (essentiellement, cela oblige l'utilisateur à passer par référence plutôt que par valeur).

0 votes

Non, il ne le fera pas, passByValue(a) ; dans main() compile bien, et moi, comme icecrime, je crois qu'il y a une copie implicite de l'objet.

0 votes

@davidnr, hmm, échoue dans gcc pour la raison mentionnée ci-dessus.

0 votes

@davidnr : Sous GCC, j'obtiens error: no matching function for call to 'A::A(A&)', error : initialisation de l'argument 1 de 'void passByValue(A)'`.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X