web-dev-qa-db-fra.com

Pourquoi l'opérateur = travaille-t-il sur des structures sans avoir été défini?

Regardons un exemple simple:

struct some_struct {
   std::string str;
   int a, b, c;
}

some_struct abc, abc_copy;
abc.str = "some text";
abc.a = 1;
abc.b = 2;
abc.c = 3;

abc_copy = abc;

Alors abc_copy est exactement copie de abc .. comment est-il possible sans de définir l'opérateur = ?

(Cela m'a pris par surprise lorsque j'ai travaillé sur un code ..)

53
conejoroy

Si vous ne définissez pas ces quatre méthodes (six en C++ 11), le compilateur les générera pour vous:

  • Constructeur par défaut
  • Copier le constructeur
  • Opérateur d'assignation
  • Destructeur
  • Déplacer le constructeur (C++ 11)
  • Déplacer l'affectation (C++ 11)

Si vous voulez savoir pourquoi?
Il s'agit de maintenir la compatibilité descendante avec C (car les structures C sont copiables en utilisant = et dans la déclaration). Mais cela facilite également l'écriture de classes simples. Certains diront que cela ajoute des problèmes en raison du "problème de copie superficielle". Mon argument contre cela est que vous ne devriez pas avoir une classe avec des pointeurs RAW possédés. En utilisant les pointeurs intelligents appropriés, ce problème disparaît.

Constructeur par défaut (si aucun autre constructeur n'est défini)

Le constructeur par défaut généré par le compilateur appellera le constructeur par défaut des classes de base, puis le constructeur par défaut de chaque membre (dans l'ordre dans lequel ils sont déclarés)

Destructeur (si aucun destructeur n'est défini)

Appelle le destructeur de chaque membre dans l'ordre inverse de la déclaration. Appelle ensuite le destructeur de la classe de base.

Constructeur de copie (si aucun constructeur de copie n'est défini)

Appelle le constructeur de copie de classe de base en passant l'objet src. Appelle ensuite le constructeur de copie de chaque membre en utilisant les membres des objets src comme valeur à copier.

Opérateur d'assignation

Appelle l'opérateur d'affectation de classe de base en passant l'objet src. Appelle ensuite l'opérateur d'affectation sur chaque membre en utilisant l'objet src comme valeur à copier.

Déplacer le constructeur (si aucun constructeur de déplacement n'est défini)

Appelle le constructeur de déplacement de classe de base en passant l'objet src. Appelle ensuite le constructeur de déplacement de chaque membre en utilisant les membres des objets src comme valeur à déplacer.

Opérateur d'affectation de déplacement

Appelle l'opérateur d'affectation de déplacement de classe de base en passant l'objet src. Appelle ensuite l'opérateur d'affectation de déplacement sur chaque membre en utilisant l'objet src comme valeur à copier.

Si vous définissez une classe comme celle-ci:

struct some_struct: public some_base
{   
    std::string str1;
    int a;
    float b;
    char* c;
    std::string str2;
};

Ce que le compilateur va construire est:

struct some_struct: public some_base
{   
    std::string str1;
    int a;
    float b;
    char* c;
    std::string str2;

    // Conceptually two different versions of the default constructor are built
    // One is for value-initialization the other for zero-initialization
    // The one used depends on how the object is declared.
    //        some_struct* a = new some_struct;     // value-initialized
    //        some_struct* b = new some_struct();   // zero-initialized
    //        some_struct  c;                       // value-initialized
    //        some_struct  d = some_struct();       // zero-initialized
    // Note: Just because there are conceptually two constructors does not mean
    //       there are actually two built.

    // value-initialize version
    some_struct()
        : some_base()            // value-initialize base (if compiler generated)
        , str1()                 // has a normal constructor so just call it
        // PODS not initialized
        , str2()
   {}

    // zero-initialize version
    some_struct()
        : some_base()            // zero-initialize base (if compiler generated)
        , str1()                 // has a normal constructor so just call it.
        , a(0)
        , b(0)
        , c(0)   // 0 is NULL
        , str2()
        // Initialize all padding to zero
   {}

    some_struct(some_struct const& copy)
        : some_base(copy)
        , str1(copy.str1)
        , a(copy.a)
        , b(copy.b)
        , c(copy.c)
        , str2(copy.str2)
    {}

    some_struct& operator=(some_struct const& copy)
    {
        some_base::operator=(copy);
        str1 = copy.str1;
        a    = copy.a;
        b    = copy.b;
        c    = copy.c;
        str2 = copy.str2;
        return *this;
    }

    ~some_struct()
    {}
    // Note the below is pseudo code
    // Also note member destruction happens after user code.
    // In the compiler generated version the user code is empty
        : ~str2()
        // PODs don't have destructor
        , ~str1()
        , ~some_base();
    // End of destructor here.

    // In C++11 we also have Move constructor and move assignment.
    some_struct(some_struct&& copy)
                    //    ^^^^  Notice the double &&
        : some_base(std::move(copy))
        , str1(std::move(copy.str1))
        , a(std::move(copy.a))
        , b(std::move(copy.b))
        , c(std::move(copy.c))
        , str2(std::move(copy.str2))
    {}

    some_struct& operator=(some_struct&& copy)
                               //    ^^^^  Notice the double &&
    {
        some_base::operator=(std::move(copy));
        str1 = std::move(copy.str1);
        a    = std::move(copy.a);
        b    = std::move(copy.b);
        c    = std::move(copy.c);
        str2 = std::move(copy.str2);
        return *this;
    } 
};
82
Martin York

En C++, les structures sont équivalentes aux classes où les membres utilisent par défaut un accès public plutôt que privé.

Les compilateurs C++ généreront également automatiquement les membres spéciaux suivants d'une classe s'ils ne sont pas fournis:

  • constructeur par défaut - pas d'arguments, par défaut tout initialise.
  • Copier constructeur - ie une méthode avec le même nom que la classe, qui prend une référence à un autre objet de la même classe. Copie toutes les valeurs à travers.
  • Destructor - Appelé lorsque l'objet est détruit. Par défaut ne fait rien.
  • Opérateur d'affectation - Appelé lorsqu'une structure/classe est affectée à une autre. Il s'agit de la méthode générée automatiquement qui est appelée dans le cas ci-dessus.
7
MHarris

Ce comportement est nécessaire pour maintenir la compatibilité des sources avec C.

C ne vous donne pas la possibilité de définir/remplacer des opérateurs, donc les structures sont normalement copiées avec l'opérateur =.

5
Ferruccio

Mais c'est défini. Dans la norme. Si vous ne fournissez aucun opérateur =, un vous est fourni. Et l'opérateur par défaut copie simplement chacune des variables membres. Et comment sait-il de quelle façon copier chaque membre? il appelle leur opérateur = (qui, s'il n'est pas défini, est fourni par défaut ...).

4
eran

L'opérateur d'affectation (operator=) est l'une des fonctions implicitement générées pour une structure ou une classe en C++.

Voici une référence décrivant les 4 membres générés implicitement:
http://www.cs.ucf.edu/~leavens/larchc++manual/lcpp_136.html

En bref, le membre généré implicitement effectue une copie superficielle membre . Voici la version longue de la page liée:

La spécification d'opérateur d'affectation générée implicitement, si nécessaire, est la suivante. La spécification indique que le résultat est l'objet affecté (self) et que la valeur de la valeur abstraite de self dans le post-état self "est la même comme valeur de la valeur abstraite de l'argument from.

// @(#)$Id: default_assignment_op.lh,v 1.3 1998/08/27 22:42:13 leavens Exp $
#include "default_interfaces.lh"

T& T::operator = (const T& from) throw();
//@ behavior {
//@   requires assigned(from, any) /\ assigned(from\any, any);
//@   modifies self;
//@   ensures result = self /\ self" = from\any\any;
//@   ensures redundantly assigned(self, post) /\ assigned(self', post);
//           thus
//@   ensures redundantly assigned(result, post) /\ assigned(result', post);
//@ }
3
Sam Harwell

Le compilateur synthétisera certains membres pour vous si vous ne les définissez pas explicitement vous-même. L'opérateur d'affectation en fait partie. Un constructeur de copie en est un autre, et vous obtenez également un destructeur. Vous obtenez également un constructeur par défaut si vous ne fournissez aucun de vos propres constructeurs. Au-delà de cela, je ne sais pas quoi d'autre, mais je pense qu'il peut y en avoir d'autres (le lien dans la réponse donnée par 280Z28 suggère cependant le contraire et je ne me souviens pas où je l'ai lu maintenant, alors peut-être que ce n'est que quatre).

2
Troubadour

les structures sont essentiellement une concaténation de ses composants en mémoire (avec un éventuel remplissage intégré pour l'alignement). Lorsque vous attribuez à une structure la valeur d'une autre, les valeurs sont simplement copiées.

0
Scott M.