Pourquoi les gens définissent-ils un constructeur de copie privée?
Quand est-ce que le constructeur de copie et l'opérateur d'affectation sont privés une bonne conception?
S'il n'y a pas de membres dans la classe qui sont des pointeurs ou des poignées vers un objet unique (comme le nom de fichier), alors dans quels autres cas existe-t-il où le constructeur de copie privée est une bonne idée?
La même question s'applique à l'opérateur d'affectation. Étant donné que la majorité du C++ tourne autour de la copie d'objets et du passage par référence, existe-t-il de bonnes conceptions impliquant un constructeur de copie privé?
Certains objets représentent des entités particulières qui ne peuvent ou ne doivent pas être copiées. Par exemple, vous pouvez empêcher la copie d'un objet qui représente le fichier journal utilisé par une application, correspondant à l'attente qu'un seul fichier journal sera utilisé par toutes les parties du code. L'utilisation d'un objet copié accidentellement ou de manière inappropriée peut conduire à l'apparition de contenu en désordre dans le journal, à des enregistrements inexacts de la taille du journal actuel, à plusieurs tentatives (certaines échouant) de "rouler" vers un nouveau nom de fichier journal ou de renommer l'existant.
Une autre utilisation consiste à appliquer la copie via une fonction virtuelle. Comme les constructeurs ne peuvent pas être virtual
, une pratique courante consiste à empêcher l'accès direct au constructeur de copie et à fournir une méthode virtual Base* clone()
qui renvoie une copie du type d'exécution réel auquel un points de pointeur. Cela empêche le découpage accidentel que Base b(derived)
présenterait.
Un autre exemple: un objet pointeur intelligent simple et mort qui supprime simplement le pointeur qui lui est donné dans le constructeur: s'il ne prend pas en charge le comptage de références ou une autre manière de gérer plusieurs propriétaires, et ne veut pas avoir de risque maladroit involontaire std::auto_ptr
le transfert de propriété de style, puis le simple fait de masquer le constructeur de copie donne un excellent petit pointeur intelligent qui est rapide et efficace pour les cas limités où il est utilisable. Une erreur de compilation lors de la tentative de copie demanderait effectivement au programmeur "hé - si vous voulez vraiment le faire, changez-moi en un pointeur partagé, sinon reculez!".
Un cas d'utilisation est le motif singleton où il ne peut y avoir qu'une seule instance d'une classe. Dans ce cas, vous devez rendre vos constructeurs et votre opérateur d'affectation = privés afin qu'il n'y ait aucun moyen de créer plus d'un objet. La seule façon de créer un objet est via votre fonction GetInstance () comme indiqué ci-dessous.
// An example of singleton pattern
class CMySingleton
{
public:
static CMySingleton& GetInstance()
{
static CMySingleton singleton;
return singleton;
}
// Other non-static member functions
private:
CMySingleton() {} // Private constructor
~CMySingleton() {}
CMySingleton(const CMySingleton&); // Prevent copy-construction
CMySingleton& operator=(const CMySingleton&); // Prevent assignment
};
int main(int argc, char* argv[])
{
// create a single instance of the class
CMySingleton &object = CMySingleton::GetInstance();
// compile fail due to private constructor
CMySingleton object1;
// compile fail due to private copy constructor
CMySingleton object2(object);
// compile fail due to private assignment operator
object1 = object;
// ..
return 0;
}
Un très mauvais exemple:
class Vehicle : { int wheels; Vehicle(int w) : wheels(w) {} }
class Car : public Vehicle { Engine * engine; public Car(Engine * e) : Vehicle(4), engine(e) }
...
Car c(new Engine());
Car c2(c); // Now both cars share the same engine!
Vehicle v;
v = c; // This doesn't even make any sense; all you have is a Vehicle with 4 wheels but no engine.
Que signifie "copier" une voiture? (Une voiture est-elle un modèle de voiture ou une instance de voiture? La copie préserve-t-elle l'immatriculation du véhicule?)
Que signifie assigner un véhicule à un autre?
Si les opérations sont dénuées de sens (ou simplement non implémentées), la chose standard à faire est de rendre le constructeur de copie et l'opérateur d'affectation privés, provoquant une erreur de compilation s'ils sont utilisés au lieu d'un comportement étrange.
Une raison courante pour rendre le constructeur de copie et l'affectation de copie privées est de désactiver l'implémentation par défaut de ces opérations. Cependant, en C++ 0x, il existe une syntaxe spéciale = supprimer à cette fin. Donc, en C++ 0x, rendre la copie privée du ctor semble être limitée à des cas très exotiques.
Les cteurs de copie et les affectations sont plutôt du sucre syntaxique; donc un tel "sucre privé" semble être le symptôme de la cupidité :)
Même si le contenu de l'objet n'est pas des pointeurs ou d'autres références, empêcher les gens de copier l'objet peut toujours être utile. La classe contient peut-être beaucoup de données et la copie est trop lourde pour une opération.
Le " idiome du constructeur virtuel " est un cas important où un constructeur de copie privé ou protégé est nécessaire. Un problème se pose en C++ où l'on vous donne le pointeur vers une classe de base, d'un objet qui est en fait hérité de cette classe de base, et que vous souhaitez en faire une copie. L'appel du constructeur de copie n'appellerait pas le constructeur de copie de la classe héritée, mais en fait appelait le constructeur de copie de la classe de base.
Observer:
class Base {
public:
Base( const Base & ref ){ std::cout << "Base copy constructor" ; }
};
class Derived : public Base {
public:
Derived( const Derived & ref ) : Base(ref) { std::cout << "Derived copy constructor"; }
}
Base * obj = new Derived;
Base * obj2 = new Derived(*obj);
Le code ci-dessus produirait la sortie:
"Base copy constructor"
Ce n'est clairement pas le comportement que le programmeur voulait! Le programmeur tentait de copier un objet de type "Derived" mais a récupéré un objet de type "Base" !!
Le problème est résolu en utilisant l'idiome susmentionné. Observez l'exemple écrit ci-dessus, réécrit pour utiliser cet idiome:
class Base {
public:
virtual Base * clone () const = 0; //this will need to be implemented by derived class
protected:
Base( const Base & ref ){ std::cout << "Base copy constructor" ; }
};
class Derived : public Base {
public:
virtual Base * clone () const {
//call private copy constructor of class "Derived"
return static_cast<Base *>( new Derived(*this) );
}
//private copy constructor:
private:
Derived( const Derived & ref ) : Base(ref) { std::cout << "Derived copy constructor"; }
}
Base * obj = new Derived;
Base * obj2 = obj->clone();
Le code ci-dessus produirait la sortie:
"Base copy constructor"
"Derived copy constructor"
En d'autres termes, l'objet qui a été construit de type souhaité "Derived", et non de type "Base"!
Comme vous pouvez le voir, dans le type Derived, le constructeur de copie a été intentionnellement rendu privé, car ce serait une mauvaise conception de l'API de donner aux programmeurs la possibilité d'essayer accidentellement d'appeler le constructeur de copie manuellement, plutôt que d'utiliser l'interface intelligente fournie par clone ( ). Autrement dit, un constructeur de copie publique directement appelable disponible pourrait amener les programmeurs à commettre l'erreur mentionnée dans la partie 1. Dans ce cas, la meilleure pratique aurait que le constructeur de copie soit masqué et accessible uniquement indirectement en utilisant la méthode "clone ( ) ".
Vous souhaiterez peut-être implémenter certaines des méthodes de la classe à l'aide d'un constructeur de copie, mais ne pas l'exposer en dehors de la classe. Alors vous le rendez privé. Comme toute autre méthode.