Qu'est-ce que la règle des trois?

2151
  • Que signifie copier un objet ?
  • Que sont le constructeur de copie et l' opérateur d'affectation de copie ?
  • Quand dois-je les déclarer moi-même?
  • Comment empêcher la copie de mes objets?
fredoverflow
la source
52
Veuillez lire tout ce fil et le c++-faqwiki du tag avant de voter pour fermer .
sbi
13
@Binary: prenez au moins le temps de lire la discussion sur les commentaires avant de voter. Le texte était beaucoup plus simple, mais Fred a été invité à le développer. De plus, bien que ce soit quatre questions grammaticalement , ce n'est vraiment qu'une seule question avec plusieurs aspects. (Si vous n'êtes pas d'accord avec cela, alors prouvez votre point de vue en répondant à chacune de ces questions et laissez-nous voter sur les résultats.)
sbi
1
Fred, voici un ajout intéressant à votre réponse concernant C ++ 1x: stackoverflow.com/questions/4782757/… . Comment gérons-nous cela?
sbi
6
Connexe: La loi des deux grands
Nemanja Trifunovic
4
Gardez à l'esprit que, depuis C ++ 11, je pense que cela a été mis à niveau vers la règle de cinq, ou quelque chose comme ça.
paxdiablo

Réponses:

1795

introduction

C ++ traite les variables de types définis par l'utilisateur avec une sémantique de valeur . Cela signifie que les objets sont implicitement copiés dans divers contextes, et nous devons comprendre ce que signifie réellement "copier un objet".

Prenons un exemple simple:

class person
{
    std::string name;
    int age;

public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
};

int main()
{
    person a("Bjarne Stroustrup", 60);
    person b(a);   // What happens here?
    b = a;         // And here?
}

(Si la partie vous laisse perplexe name(name), age(age), cela s'appelle une liste d'initialisation de membre .)

Fonctions spéciales des membres

Que signifie copier un personobjet? La mainfonction affiche deux scénarios de copie distincts. L'initialisation person b(a);est effectuée par le constructeur de copie . Son travail consiste à construire un nouvel objet en fonction de l'état d'un objet existant. L'affectation b = aest effectuée par l' opérateur d'affectation de copie . Son travail est généralement un peu plus compliqué, car l'objet cible est déjà dans un état valide qui doit être traité.

Puisque nous n'avons déclaré ni le constructeur de copie ni l'opérateur d'affectation (ni le destructeur) nous-mêmes, ceux-ci sont implicitement définis pour nous. Citation de la norme:

Le constructeur de copie et l'opérateur d'affectation de copie, [...] et le destructeur sont des fonctions membres spéciales. [ Remarque : L'implémentation déclarera implicitement ces fonctions membres pour certains types de classe lorsque le programme ne les déclare pas explicitement. L'implémentation les définira implicitement si elles sont utilisées. [...] note de fin ] [n3126.pdf section 12 §1]

Par défaut, copier un objet signifie copier ses membres:

Le constructeur de copie défini implicitement pour une classe non-union X effectue une copie membre par membre de ses sous-objets. [n3126.pdf section 12.8 §16]

L'opérateur d'affectation de copie défini implicitement pour une classe non-union X effectue l'affectation de copie par membre de ses sous-objets. [n3126.pdf section 12.8 §30]

Définitions implicites

Les fonctions membres spéciales définies implicitement pour personressemblent à ceci:

// 1. copy constructor
person(const person& that) : name(that.name), age(that.age)
{
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    name = that.name;
    age = that.age;
    return *this;
}

// 3. destructor
~person()
{
}

La copie par membre est exactement ce que nous voulons dans ce cas: nameet ageest copiée, nous obtenons donc un personobjet indépendant et indépendant . Le destructeur défini implicitement est toujours vide. C'est également très bien dans ce cas puisque nous n'avons acquis aucune ressource dans le constructeur. Les destructeurs des membres sont implicitement appelés une fois le persondestructeur terminé:

Après avoir exécuté le corps du destructeur et détruit tous les objets automatiques alloués dans le corps, un destructeur pour la classe X appelle les destructeurs des membres directs de X [n3126.pdf 12.4 §6]

Gérer les ressources

Alors, quand devrions-nous déclarer explicitement ces fonctions membres spéciales? Lorsque notre classe gère une ressource , c'est-à-dire lorsqu'un objet de la classe est responsable de cette ressource. Cela signifie généralement que la ressource est acquise dans le constructeur (ou transmise au constructeur) et libérée dans le destructeur.

Remontons le temps au C ++ pré-standard. Il n'y avait rien de tel std::string, et les programmeurs étaient amoureux des pointeurs. La personclasse aurait pu ressembler à ceci:

class person
{
    char* name;
    int age;

public:

    // the constructor acquires a resource:
    // in this case, dynamic memory obtained via new[]
    person(const char* the_name, int the_age)
    {
        name = new char[strlen(the_name) + 1];
        strcpy(name, the_name);
        age = the_age;
    }

    // the destructor must release this resource via delete[]
    ~person()
    {
        delete[] name;
    }
};

Aujourd'hui encore, les gens écrivent des cours dans ce style et ont des ennuis: " J'ai poussé une personne dans un vecteur et maintenant j'ai des erreurs de mémoire folles! " Rappelez-vous que par défaut, copier un objet signifie copier ses membres, mais copier le namemembre simplement copie un pointeur, pas le tableau de caractères vers lequel il pointe! Cela a plusieurs effets désagréables:

  1. Les modifications via apeuvent être observées via b.
  2. Une fois bdétruit, se a.nametrouve un pointeur suspendu.
  3. Si aest détruit, la suppression du pointeur pendant entraîne un comportement indéfini .
  4. Étant donné que l'affectation ne prend pas en compte ce qui était nameindiqué avant l'affectation, tôt ou tard, vous obtiendrez des fuites de mémoire partout.

Définitions explicites

Étant donné que la copie par membre n'a pas l'effet souhaité, nous devons définir explicitement le constructeur de copie et l'opérateur d'affectation de copie pour effectuer des copies complètes du tableau de caractères:

// 1. copy constructor
person(const person& that)
{
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    if (this != &that)
    {
        delete[] name;
        // This is a dangerous point in the flow of execution!
        // We have temporarily invalidated the class invariants,
        // and the next statement might throw an exception,
        // leaving the object in an invalid state :(
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }
    return *this;
}

Notez la différence entre l'initialisation et l'affectation: nous devons supprimer l'ancien état avant de l'affecter namepour éviter les fuites de mémoire. Nous devons également nous protéger contre l'auto-affectation du formulaire x = x. Sans cette vérification, delete[] namesupprimerait le tableau contenant la source de chaîne, parce que quand vous écrivez x = x, à la fois this->nameet that.namecontiennent le même pointeur.

Sécurité d'exception

Malheureusement, cette solution échouera si new char[...]lève une exception en raison de l'épuisement de la mémoire. Une solution possible consiste à introduire une variable locale et à réorganiser les instructions:

// 2. copy assignment operator
person& operator=(const person& that)
{
    char* local_name = new char[strlen(that.name) + 1];
    // If the above statement throws,
    // the object is still in the same state as before.
    // None of the following statements will throw an exception :)
    strcpy(local_name, that.name);
    delete[] name;
    name = local_name;
    age = that.age;
    return *this;
}

Cela prend également en charge l'auto-affectation sans vérification explicite. Une solution encore plus robuste à ce problème est l' idiome de copie et d'échange , mais je n'entrerai pas dans les détails de la sécurité des exceptions ici. J'ai seulement mentionné des exceptions pour faire le point suivant: Écrire des classes qui gèrent des ressources est difficile.

Ressources non copiables

Certaines ressources ne peuvent pas ou ne doivent pas être copiées, comme les descripteurs de fichiers ou les mutex. Dans ce cas, déclarez simplement le constructeur de copie et l'opérateur d'affectation de copie comme privatesans donner de définition:

private:

    person(const person& that);
    person& operator=(const person& that);

Alternativement, vous pouvez les hériter boost::noncopyableou les déclarer supprimés (en C ++ 11 et supérieur):

person(const person& that) = delete;
person& operator=(const person& that) = delete;

La règle des trois

Parfois, vous devez implémenter une classe qui gère une ressource. (Ne gérez jamais plusieurs ressources dans une seule classe, cela ne fera que causer de la douleur.) Dans ce cas, rappelez-vous la règle des trois :

Si vous devez déclarer vous-même explicitement le destructeur, le constructeur de copie ou l'opérateur d'affectation de copie, vous devez probablement déclarer explicitement les trois.

(Malheureusement, cette "règle" n'est pas appliquée par la norme C ++ ou par tout compilateur que je connaisse.)

La règle des cinq

À partir de C ++ 11, un objet possède 2 fonctions membres spéciales supplémentaires: le constructeur de déplacement et l'affectation de déplacement. La règle des cinq États met également en œuvre ces fonctions.

Un exemple avec les signatures:

class person
{
    std::string name;
    int age;

public:
    person(const std::string& name, int age);        // Ctor
    person(const person &) = default;                // Copy Ctor
    person(person &&) noexcept = default;            // Move Ctor
    person& operator=(const person &) = default;     // Copy Assignment
    person& operator=(person &&) noexcept = default; // Move Assignment
    ~person() noexcept = default;                    // Dtor
};

La règle du zéro

La règle de 3/5 est également appelée la règle de 0/3/5. La partie zéro de la règle indique que vous êtes autorisé à n'écrire aucune des fonctions membres spéciales lors de la création de votre classe.

Conseil

La plupart du temps, vous n'avez pas besoin de gérer vous-même une ressource, car une classe existante telle que std::stringdéjà le fait pour vous. Comparez simplement le code simple utilisant un std::stringmembre à l'alternative alambiquée et sujette aux erreurs en utilisant un char*et vous devriez être convaincu. Tant que vous restez à l'écart des membres du pointeur brut, la règle de trois ne concernera probablement pas votre propre code.

fredoverflow
la source
4
Fred, je me sentirais mieux à propos de mon vote positif si (A) vous n'écriviez pas une affectation mal implémentée dans du code copiable et ajoutiez une note disant qu'elle est incorrecte et regardez ailleurs dans les petits caractères; soit utilisez c & s dans le code, soit sautez simplement l'implémentation de tous ces membres (B), vous raccourciriez la première moitié, ce qui n'a pas grand-chose à voir avec le RoT; (C) vous discuteriez de l'introduction de la sémantique des mouvements et de ce que cela signifie pour le RoT.
sbi
7
Mais alors le poste devrait être fait C / W, je pense. J'aime que vous gardiez les termes pour la plupart précis (c'est-à-dire que vous dites " opérateur d'affectation de copie ", et que vous ne tiriez pas dans le piège commun que l'affectation ne pouvait pas impliquer une copie).
Johannes Schaub - litb
4
@Prasoon: Je ne pense pas que supprimer la moitié de la réponse serait considéré comme une "correction juste" d'une réponse non CW.
sbi
69
Ce serait génial si vous mettez à jour votre message pour C ++ 11 (c.-à-d. Déplacer constructeur / affectation)
Alexander Malakhov
5
@solalito Tout ce que vous devez libérer après utilisation: verrous d'accès simultané, descripteurs de fichiers, connexions à la base de données, sockets réseau, mémoire de tas ...
fredoverflow
510

La règle des trois est une règle de base pour C ++, en disant essentiellement

Si votre classe a besoin de

  • un constructeur de copie ,
  • un opérateur d'affectation ,
  • ou un destructeur ,

définis de manière explicite, il faudra alors probablement les trois .

La raison en est que les trois sont généralement utilisés pour gérer une ressource, et si votre classe gère une ressource, elle doit généralement gérer la copie ainsi que la libération.

S'il n'y a pas de bonne sémantique pour copier la ressource gérée par votre classe, alors envisagez d'interdire la copie en déclarant (pas en définissant ) le constructeur de copie et l'opérateur d'affectation comme private.

(Notez que la nouvelle version à venir de la norme C ++ (qui est C ++ 11) ajoute la sémantique de déplacement à C ++, ce qui changera probablement la règle des trois. Cependant, je sais trop peu de choses à ce sujet pour écrire une section C ++ 11 à propos de la règle de trois.)

sbi
la source
3
Une autre solution pour empêcher la copie consiste à hériter (en privé) d'une classe qui ne peut pas être copiée (comme boost::noncopyable). Cela peut aussi être beaucoup plus clair. Je pense que C ++ 0x et la possibilité de "supprimer" des fonctions pourraient aider ici, mais j'ai oublié la syntaxe: /
Matthieu M.
2
@Matthieu: Oui, ça marche aussi. Mais à moins de faire noncopyablepartie de la librairie standard, je ne considère pas cela comme une grande amélioration. (Oh, et si vous avez oublié la syntaxe de suppression, vous avez oublié plus que je n'ai jamais connu. :))
sbi
3
@Daan: Voir cette réponse . Cependant, je recommanderais de m'en tenir à la règle de zéro de Martinho . Pour moi, c'est l'une des règles de base les plus importantes pour le C ++ inventée au cours de la dernière décennie.
sbi
3
La règle de zéro de Martinho maintenant meilleure (sans prise de contrôle apparente de l'adware) située sur archive.org
Nathan Kidd
161

La loi des trois grands est celle indiquée ci-dessus.

Un exemple simple, en anglais simple, du type de problème qu'il résout:

Destructeur non par défaut

Vous avez alloué de la mémoire dans votre constructeur et vous devez donc écrire un destructeur pour le supprimer. Sinon, vous provoquerez une fuite de mémoire.

Vous pourriez penser que c'est du travail accompli.

Le problème sera, si une copie est faite de votre objet, alors la copie pointera vers la même mémoire que l'objet d'origine.

Une fois que l'un d'eux supprime la mémoire de son destructeur, l'autre aura un pointeur vers une mémoire invalide (c'est ce qu'on appelle un pointeur suspendu) lorsqu'il essaie de l'utiliser, les choses vont devenir poilues.

Par conséquent, vous écrivez un constructeur de copie afin qu'il alloue aux nouveaux objets leurs propres morceaux de mémoire à détruire.

Opérateur d'affectation et constructeur de copie

Vous avez alloué de la mémoire dans votre constructeur à un pointeur membre de votre classe. Lorsque vous copiez un objet de cette classe, l'opérateur d'affectation par défaut et le constructeur de copie copient la valeur de ce pointeur de membre vers le nouvel objet.

Cela signifie que le nouvel objet et l'ancien objet pointeront vers le même morceau de mémoire, donc lorsque vous le modifiez dans un objet, il sera également modifié pour l'autre objet. Si un objet supprime cette mémoire, l'autre continuera d'essayer de l'utiliser - eek.

Pour résoudre ce problème, vous écrivez votre propre version du constructeur de copie et de l'opérateur d'affectation. Vos versions allouent une mémoire distincte aux nouveaux objets et copient les valeurs vers lesquelles pointe le premier pointeur plutôt que son adresse.

Stefan
la source
4
Donc, si nous utilisons un constructeur de copie, la copie est effectuée, mais à un emplacement de mémoire différent et si nous n'utilisons pas de constructeur de copie, la copie est effectuée mais elle pointe vers le même emplacement de mémoire. c'est ce que vous essayez de dire? Ainsi, une copie sans constructeur de copie signifie qu'un nouveau pointeur sera là mais pointant vers le même emplacement mémoire, mais si nous avons un constructeur de copie explicitement défini par l'utilisateur, nous aurons un pointeur distinct pointant vers un emplacement mémoire différent mais contenant les données.
Incassable le
4
Désolé, j'ai répondu à cela il y a longtemps, mais ma réponse ne semble pas encore être là :-( Fondamentalement, oui - vous l'avez :-)
Stefan
1
Comment le principe s'applique-t-il à l'opérateur d'affectation de copie? Cette réponse serait plus utile si la 3e de la règle de trois était mentionnée.
DBedrenko
1
@DBedrenko, "vous écrivez un constructeur de copie pour qu'il alloue aux nouveaux objets leurs propres morceaux de mémoire ..." c'est le même principe qui s'étend à l'opérateur d'affectation de copie. Ne pensez-vous pas que je l'ai dit clairement?
Stefan
2
@DBedrenko, j'ai ajouté plus d'informations. Est-ce que c'est plus clair?
Stefan
44

Fondamentalement, si vous avez un destructeur (pas le destructeur par défaut), cela signifie que la classe que vous avez définie dispose d'une allocation de mémoire. Supposons que la classe soit utilisée à l'extérieur par un code client ou par vous.

    MyClass x(a, b);
    MyClass y(c, d);
    x = y; // This is a shallow copy if assignment operator is not provided

Si MyClass n'a que quelques membres de type primitif, un opérateur d'affectation par défaut fonctionnerait, mais s'il a des membres de pointeur et des objets qui n'ont pas d'opérateurs d'affectation, le résultat serait imprévisible. Par conséquent, nous pouvons dire que s'il y a quelque chose à supprimer dans le destructeur d'une classe, nous pourrions avoir besoin d'un opérateur de copie en profondeur, ce qui signifie que nous devrions fournir un constructeur de copie et un opérateur d'affectation.

fatma.ekici
la source
36

Que signifie copier un objet? Il existe plusieurs façons de copier des objets - parlons des 2 types auxquels vous faites probablement référence - la copie profonde et la copie superficielle.

Puisque nous sommes dans un langage orienté objet (ou du moins le supposons), disons que vous avez un morceau de mémoire alloué. Puisqu'il s'agit d'un langage OO, nous pouvons facilement faire référence aux morceaux de mémoire que nous allouons car ce sont généralement des variables primitives (ints, chars, octets) ou des classes que nous avons définies qui sont faites de nos propres types et primitives. Disons donc que nous avons une classe de voitures comme suit:

class Car //A very simple class just to demonstrate what these definitions mean.
//It's pseudocode C++/Javaish, I assume strings do not need to be allocated.
{
private String sPrintColor;
private String sModel;
private String sMake;

public changePaint(String newColor)
{
   this.sPrintColor = newColor;
}

public Car(String model, String make, String color) //Constructor
{
   this.sPrintColor = color;
   this.sModel = model;
   this.sMake = make;
}

public ~Car() //Destructor
{
//Because we did not create any custom types, we aren't adding more code.
//Anytime your object goes out of scope / program collects garbage / etc. this guy gets called + all other related destructors.
//Since we did not use anything but strings, we have nothing additional to handle.
//The assumption is being made that the 3 strings will be handled by string's destructor and that it is being called automatically--if this were not the case you would need to do it here.
}

public Car(const Car &other) // Copy Constructor
{
   this.sPrintColor = other.sPrintColor;
   this.sModel = other.sModel;
   this.sMake = other.sMake;
}
public Car &operator =(const Car &other) // Assignment Operator
{
   if(this != &other)
   {
      this.sPrintColor = other.sPrintColor;
      this.sModel = other.sModel;
      this.sMake = other.sMake;
   }
   return *this;
}

}

Une copie profonde est si nous déclarons un objet puis créons une copie complètement séparée de l'objet ... nous nous retrouvons avec 2 objets dans 2 ensembles de mémoire complètement.

Car car1 = new Car("mustang", "ford", "red");
Car car2 = car1; //Call the copy constructor
car2.changePaint("green");
//car2 is now green but car1 is still red.

Faisons maintenant quelque chose d'étrange. Disons que car2 est mal programmé ou intentionnellement destiné à partager la mémoire réelle de car1. (C'est généralement une erreur de le faire et dans les classes, c'est généralement la couverture dont il est question.) Imaginez que chaque fois que vous posez des questions sur car2, vous résolvez vraiment un pointeur vers l'espace mémoire de car1 ... c'est plus ou moins ce qu'une copie superficielle est.

//Shallow copy example
//Assume we're in C++ because it's standard behavior is to shallow copy objects if you do not have a constructor written for an operation.
//Now let's assume I do not have any code for the assignment or copy operations like I do above...with those now gone, C++ will use the default.

 Car car1 = new Car("ford", "mustang", "red"); 
 Car car2 = car1; 
 car2.changePaint("green");//car1 is also now green 
 delete car2;/*I get rid of my car which is also really your car...I told C++ to resolve 
 the address of where car2 exists and delete the memory...which is also
 the memory associated with your car.*/
 car1.changePaint("red");/*program will likely crash because this area is
 no longer allocated to the program.*/

Donc, quelle que soit la langue dans laquelle vous écrivez, faites très attention à ce que vous voulez dire quand il s'agit de copier des objets, car la plupart du temps vous voulez une copie complète.

Que sont le constructeur de copie et l'opérateur d'affectation de copie? Je les ai déjà utilisés ci-dessus. Le constructeur de copie est appelé lorsque vous tapez du code tel que Car car2 = car1; Essentiellement si vous déclarez une variable et l'affectez sur une seule ligne, c'est alors que le constructeur de copie est appelé. L'opérateur d'affectation est ce qui se passe lorsque vous utilisez un signe égal - car2 = car1;. L'avis car2n'est pas déclaré dans la même déclaration. Les deux morceaux de code que vous écrivez pour ces opérations sont probablement très similaires. En fait, le modèle de conception typique a une autre fonction que vous appelez pour tout définir une fois que vous êtes convaincu que la copie / affectation initiale est légitime - si vous regardez le code long que j'ai écrit, les fonctions sont presque identiques.

Quand dois-je les déclarer moi-même? Si vous n'écrivez pas de code à partager ou à produire d'une manière ou d'une autre, vous n'avez vraiment besoin de les déclarer que lorsque vous en avez besoin. Vous devez être conscient de ce que fait votre langage de programme si vous choisissez de l'utiliser «par accident» et n'en avez pas créé - c'est-à-dire que vous obtenez le compilateur par défaut. J'utilise rarement des constructeurs de copie par exemple, mais les remplacements d'opérateurs d'affectation sont très courants. Saviez-vous que vous pouvez également remplacer ce que signifie addition, soustraction, etc.?

Comment empêcher la copie de mes objets? Remplacer toutes les façons d'allouer de la mémoire à votre objet avec une fonction privée est un début raisonnable. Si vous ne voulez vraiment pas que les gens les copient, vous pouvez le rendre public et alerter le programmeur en lançant une exception et en ne copiant pas l'objet.

user1701047
la source
5
La question était étiquetée C ++. Cette exposition de pseudo-code ne fait rien pour clarifier quoi que ce soit au sujet de la "Règle des Trois" bien définie au mieux, et ne fait que semer la confusion au pire.
sehe
26

Quand dois-je les déclarer moi-même?

La règle de trois stipule que si vous déclarez

  1. constructeur de copie
  2. opérateur d'affectation de copie
  3. destructeur

alors vous devez déclarer les trois. Il est né de l'observation que la nécessité de reprendre le sens d'une opération de copie provenait presque toujours de la classe effectuant une sorte de gestion des ressources, et cela impliquait presque toujours que

  • quelle que soit la gestion des ressources effectuée dans une opération de copie, elle devait probablement l'être dans l'autre opération de copie et

  • le destructeur de classe participerait également à la gestion de la ressource (la libérant généralement). La ressource classique à gérer était la mémoire, et c'est pourquoi toutes les classes Standard Library qui gèrent la mémoire (par exemple, les conteneurs STL qui effectuent la gestion dynamique de la mémoire) déclarent toutes «les trois grands»: à la fois des opérations de copie et un destructeur.

Une conséquence de la règle de trois est que la présence d'un destructeur déclaré par l'utilisateur indique qu'une copie simple par membre est peu susceptible d'être appropriée pour les opérations de copie dans la classe. Cela, à son tour, suggère que si une classe déclare un destructeur, les opérations de copie ne devraient probablement pas être générées automatiquement, car elles ne feraient pas la bonne chose. Au moment où C ++ 98 a été adopté, l'importance de ce raisonnement n'était pas pleinement appréciée, donc en C ++ 98, l'existence d'un destructeur déclaré par l'utilisateur n'a eu aucun impact sur la volonté des compilateurs de générer des opérations de copie. Cela continue d'être le cas en C ++ 11, mais uniquement parce que restreindre les conditions dans lesquelles les opérations de copie sont générées casserait trop de code hérité.

Comment empêcher la copie de mes objets?

Déclarez le constructeur de copie et l'opérateur d'affectation de copie en tant que spécificateur d'accès privé.

class MemoryBlock
{
public:

//code here

private:
MemoryBlock(const MemoryBlock& other)
{
   cout<<"copy constructor"<<endl;
}

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
 return *this;
}
};

int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}

Dans C ++ 11 et suivants, vous pouvez également déclarer le constructeur de copie et l'opérateur d'affectation supprimés

class MemoryBlock
{
public:
MemoryBlock(const MemoryBlock& other) = delete

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other) =delete
};


int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}
Ajay yadav
la source
16

De nombreuses réponses existantes concernent déjà le constructeur de copie, l'opérateur d'affectation et le destructeur. Cependant, dans le post C ++ 11, l'introduction de la sémantique de mouvement peut étendre cela au-delà de 3.

Récemment, Michael Claisse a donné une conférence qui touche à ce sujet: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class

wei
la source
10

La règle de trois en C ++ est un principe fondamental de la conception et du développement de trois exigences selon lesquelles s'il existe une définition claire dans l'une des fonctions membres suivantes, le programmeur doit définir les deux autres fonctions membres ensemble. A savoir les trois fonctions membres suivantes sont indispensables: destructeur, constructeur de copie, opérateur d'affectation de copie.

Le constructeur de copie en C ++ est un constructeur spécial. Il est utilisé pour créer un nouvel objet, qui est le nouvel objet équivalent à une copie d'un objet existant.

L'opérateur d'affectation de copie est un opérateur d'affectation spécial qui est généralement utilisé pour spécifier un objet existant à d'autres du même type d'objet.

Il y a des exemples rapides:

// default constructor
My_Class a;

// copy constructor
My_Class b(a);

// copy constructor
My_Class c = a;

// copy assignment operator
b = a;
Marcus Thornton
la source
7
Bonjour, votre réponse n'ajoute rien de nouveau. Les autres couvrent le sujet de manière beaucoup plus approfondie et plus précise - votre réponse est approximative et en fait erronée à certains endroits (à savoir qu'il n'y a pas de "must" ici; c'est "devrait très probablement"). Cela ne vaut vraiment pas la peine de publier ce type de réponse à des questions auxquelles on a déjà répondu en profondeur. Sauf si vous avez de nouvelles choses à ajouter.
Mat
1
En outre, il y a quatre exemples rapides, qui sont en quelque sorte liés à deux des trois dont parle la règle de trois. Trop de confusion.
anatolyg