Comportement du constructeur de copie explicite et utilisations pratiques

Une question récente m’a amené à me poser des questions sur les constructeurs de copies explicites. Voici un exemple de code que j’ai essayé de comstackr sous Visual Studio 2005:

struct A { A() {} explicit A(const A &) {} }; // #1 > Compilation error (expected behavior) A retByValue() { return A(); } // #2 > Comstacks 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 pour les questions:

  • Le n ° 2 est-il autorisé par la norme? Si tel est le cas, quelle est la section pertinente décrivant cette situation?
  • Connaissez-vous une utilisation pratique pour 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 comstack pas ce code et je pense qu’il a raison de ne pas le faire.

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

Un constructeur explicite construit des objects exactement comme des constructeurs non explicites, mais uniquement lorsque la syntaxe d’initialisation directe ( 8.5 ) ou les casts ( 5.2.9 , 5.4 ) sont utilisés explicitement. 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 de valeur ( 8.5 ).

et § 8.5 12:

L’initialisation qui survient lors du passage d’arguments, du retour de fonction, du lancement d’une exception ( 15.1 ), du traitement d’une exception ( 15.3 ) et des listes d’initialiseurs entre accolades ( 8.5.1 ) est appelée initialisation par copie et équivaut au formulaire.

  T x = a; 

L’initialisation qui apparaît dans les nouvelles expressions ( 5.3.4 ), les expressions static_cast ( 5.2.9 ), les conversions de types de notation fonctionnelle ( 5.2.3 ) et les initialiseurs de base et de membre ( 12.6.2 ) est appelée initialisation directe et équivaut à la forme

  T x(a); 

L’appel de 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.

La définition de passByValue est OK, car aucune instruction ne copie un object A. Dans la définition de retByValue il existe bien sûr une instruction return qui copie un object A.

Avant C ++ 11, il est pratique d’utiliser explicitement un constructeur de copie dans le cas où il s’agit en réalité de rendre une classe non copiable.

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

Le rendre aussi explicite réduit les risques, car le compilateur pourrait bien récupérer votre copie non intentionnelle et indiquer la ligne où vous le faites.

En C ++ 11 (et 14), cette opération est inutile lorsque vous utilisez la syntaxe =delete car vous obtiendrez une erreur du compilateur, même si vous effectuez une copie dans la classe même ou chez un ami.

Pour développer la réponse de @MSalters (qui est correcte), si vous passByValue(a); append passByValue(a); à votre fonction main() , le compilateur devrait se plaindre à ce sujet.

Les constructeurs de copie explicite empêchent exactement cela, c’est-à-dire empêchent la copie implicite de ressources dans les appels de fonction, etc. (essentiellement, ils obligent l’utilisateur à passer par référence plutôt que par valeur).