web-dev-qa-db-fra.com

Opérateur d'affectation par défaut = en c ++ est une copie superficielle?

Juste une simple question rapide à laquelle je n'ai pas trouvé de réponse solide ailleurs. L'opérateur par défaut = est-il juste une copie superficielle de tous les membres de la classe sur le côté droit?

Class foo {
public:
  int a, b, c;
};

foo f1, f2;
...
f1 = f2;

serait identique à:

f1.a = f2.a;
f1.b = f2.b;
f1.c = f2.c;

Cela semble être vrai lorsque je le teste, mais je dois être sûr de ne pas manquer un cas spécifique.

41
Christopher Dorian

Je dirais, par défaut operator= est un copie. Il copie chaque membre.

La distinction entre une copie superficielle et une copie profonde ne se produit que si les membres copiés sont une sorte d'indirection telle qu'un pointeur. En ce qui concerne la valeur par défaut operator= est concerné, c'est au membre qui doit être copié ce que "copier" signifie, cela peut être profond ou peu profond.

Plus précisément, cependant, la copie d'un pointeur brut ne fait que copier la valeur du pointeur, cela ne fait rien avec le referand. Par conséquent, les objets contenant des membres de pointeur sont copiés en profondeur par défaut operator=.

Il existe différents efforts pour écrire des pointeurs intelligents qui effectuent des opérations de clonage lors de la copie, donc si vous les utilisez partout à la place des pointeurs bruts, la valeur par défaut operator= effectuera une copie complète.

Si votre objet a des conteneurs standard en tant que membres, il peut être déroutant pour (par exemple) un programmeur Java de dire que operator= est une "copie superficielle". Dans Java un membre Vector est vraiment juste une référence, donc "copie superficielle" signifie que les membres Vector ne sont pas clonés: source et destination font référence à la même objet vectoriel sous-jacent. En C++, un membre vectorsera être copié, ainsi que son contenu, car le membre est un objet réel et non une référence (et vector::operator= garantit que le contenu est copié avec celui-ci).

Si votre membre de données est un vecteur de pointeurs, vous n'avez pas non plus de copie complète o une copie superficielle. Vous avez une copie semi-profonde, où les objets source et destination ont des vecteurs distincts, mais les éléments vectoriels correspondants de chacun pointent toujours vers le même objet non cloné.

39
Steve Jessop

Oui, par défaut operator= est une copie superficielle.

Soit dit en passant, la différence réelle entre shallow copy et deep copy devient visible lorsque la classe a pointeurs comme champs membres. En l'absence de pointeurs, il n'y a pas de différence (au meilleur de ma connaissance)!

Pour connaître la différence entre eux, consultez ces rubriques (sur stackoverflow lui-même):

15
Nawaz

Oui, il copie simplement l'objet au niveau des membres, ce qui peut entraîner des problèmes pour les pointeurs bruts.

9
Mark B

la copie "superficielle" contre "profonde" est moins significative en C++ qu'en C ou Java.

Pour illustrer cela, j'ai changé votre classe Foo de trois ints en une int, une int*, et un vector<int>:

#include <iostream>
#include <vector>

class Foo {
public:
  int a;
  int *b;
  std::vector<int> c;
};

using namespace std;

int main() {
  Foo f1, f2;
  f1.a = 42;
  f1.b = new int(42);
  f1.c.Push_back(42);
  f2 = f1;

  cout << "f1.b: " << f1.b << " &f1.c[0]: " << &f1.c[0] << endl;
  cout << "f2.b: " << f2.b << " &f2.c[0]: " << &f2.c[0] << endl;
}

Lorsque ce programme est exécuté, il donne la sortie suivante:

f1.b: 0x100100080 &f1.c[0]: 0x100100090
f2.b: 0x100100080 &f2.c[0]: 0x1001000a0

Le int est ennuyeux, donc je l'ai laissé de côté. Mais regardez la différence entre le int* et le vector<int>: les int* est le même en f1 et f2; c'est ce que vous appelleriez une "copie superficielle". Le vector<int> est cependant différent entre f1 et f2; c'est ce que vous appelleriez une "copie complète".

Ce qui s'est réellement passé ici est que la valeur par défaut operator = en C++ se comporte comme si le operator = pour tous ses membres ont été appelés dans l'ordre. Le operator = pour ints, int*s et d'autres types primitifs n'est qu'une copie superficielle d'octets. Le operator = pour vector<T> effectue une copie complète.

Je dirais donc que la réponse à la question est: Non, l'opérateur d'affectation par défaut en C++ n'effectue pas de copie superficielle. Mais il n'effectue pas non plus une copie complète. L'opérateur d'affectation par défaut en C++ applique récursivement les opérateurs d'affectation des membres de la classe.

7
Jack Saalwächter

Personnellement, j'aime l'explication dans Accelerated c ++ . Le texte (page 201) dit:

Si l'auteur de la classe ne spécifie pas l'opérateur d'affectation, le compilateur synthétise une version par défaut. La version par défaut est définie pour fonctionner récursivement - en affectant chaque élément de données selon les règles appropriées pour le type de cet élément. Chaque membre d'un type de classe est affecté en appelant l'opérateur d'affectation de ce membre. Les membres de type intégré sont affectés en affectant leurs valeurs.

Comme les membres de la question sont des nombres entiers, je comprends qu'ils sont copiés en profondeur. L'adaptation suivante de votre exemple illustre cela:

#include<iostream>
class foo {
public:
  int a, b, c;
};

int main() {
    foo f1, f2;
    f1 = f2;
    std::cout << "f1.a and f2.a are: " << f1.a << " and " << f2.a << std::endl;
    f2.a = 0;
    std::cout << "now, f1.a and f2.a are: " << f1.a << " and " << f2.a << std::endl;
}

qui imprime:

f1.a and f2.a are: 21861 and 21861
now, f1.a and f2.a are: 21861 and 0
4
fabian

Si a, b et c étaient des classes, l'opérateur d'affectation de ces classes serait appelé, donc le compilateur ne copie pas simplement le contenu de la mémoire brute - mais comme d'autres l'ont souligné, tous les pointeurs bruts seront copiés sans aucune tentative de duplication de la chose pointée, vous donnant ainsi le potentiel de pointeurs pendants.

1
TimA

Non. operator= n'effectue aucune copie. C'est un opérateur affectation, et non copie.

L'opérateur d'affectation par défaut affecte chaque membre.

1
Edward Strange

Comme illustré par l'extrait de code ci-dessous, l'opérateur = (affectation) pour STL effectue une copie complète.

#include <iostream>
#include <stack>
#include <map>
#include <vector>

using namespace std;

int main(int argc, const char * argv[]) {
    /* performs deep copy */
    map <int, stack<int> > m;
    stack <int> s1;
    stack <int> s2;

    s1.Push(10);
    cout<<&s1<<" "<<&(s1.top())<<" "<<s1.top()<<endl;   //0x7fff5fbfe478 0x100801200 10

    m.insert(make_pair(0, s1));
    cout<<&m[0]<<" "<<&(m[0].top())<<" "<<m[0].top()<<endl; //0x100104248 0x100803200 10

    m[0].top() = 1;
    cout<<&m[0]<<" "<<&(m[0].top())<<" "<<m[0].top()<<endl; //0x100104248 0x100803200 1

    s2 = m[0];
    cout<<&s2<<" "<<&(s2.top())<<" "<<s2.top()<<endl;   //0x7fff5fbfe448 0x100804200 1

    s2.top() = 5;
    cout<<&s2<<" "<<&(s2.top())<<" "<<s2.top()<<endl;   //0x7fff5fbfe448 0x100804200 5
    cout<<&m[0]<<" "<<&(m[0].top())<<" "<<m[0].top()<<endl; //0x100104248 0x100803200 1

    cout<<endl<<endl;

    map <int, stack<int*> > mp;
    stack <int*> s1p;
    stack <int*> s2p;

    s1p.Push(new int);
    cout<<&s1p<<" "<<&(s1p.top())<<" "<<s1p.top()<<endl;    //0x7fff5fbfe360 0x100805200 0x100104290

    mp.insert(make_pair(0, s1p));
    cout<<&mp[0]<<" "<<&(mp[0].top())<<" "<<mp[0].top()<<endl;  //0x1001042e8 0x100806200 0x100104290

    mp[0].top() = new int;
    cout<<&mp[0]<<" "<<&(mp[0].top())<<" "<<mp[0].top()<<endl;  //0x1001042e8 0x100806200 0x100104320

    s2p = mp[0];
    cout<<&s2p<<" "<<&(s2p.top())<<" "<<s2p.top()<<endl;    //0x7fff5fbfe330 0x100807200 0x100104320

    s2p.top() = new int;
    cout<<&s2p<<" "<<&(s2p.top())<<" "<<s2p.top()<<endl;    //0x7fff5fbfe330 0x100807200 0x100104340
    cout<<&mp[0]<<" "<<&(mp[0].top())<<" "<<mp[0].top()<<endl;  //0x1001042e8 0x100806200 0x100104320

    cout<<endl<<endl;

    vector<int> v1,v2;
    vector<int*> v1p, v2p;

    v1.Push_back(1);
    cout<<&v1<<" "<<&v1[0]<<" "<<v1[0]<<endl;   //0x7fff5fbfe290 0x100104350 1

    v2 = v1;
    cout<<&v2<<" "<<&v2[0]<<" "<<v2[0]<<endl;   //0x7fff5fbfe278 0x100104360 1

    v2[0] = 10;
    cout<<&v2<<" "<<&v2[0]<<" "<<v2[0]<<endl;   //0x7fff5fbfe278 0x100104360 10
    cout<<&v1<<" "<<&v1[0]<<" "<<v1[0]<<endl;   //0x7fff5fbfe290 0x100104350 1

    cout<<endl<<endl;

    v1p.Push_back(new int);
    cout<<&v1p<<" "<<&v1p[0]<<" "<<v1p[0]<<endl;    //0x7fff5fbfe260 0x100104380 0x100104370

    v2p = v1p;
    cout<<&v2p<<" "<<&v2p[0]<<" "<<v2p[0]<<endl;    //0x7fff5fbfe248 0x100104390 0x100104370

    v2p[0] = new int;
    cout<<&v2p<<" "<<&v2p[0]<<" "<<v2p[0]<<endl;    //0x7fff5fbfe248 0x100104390 0x1001043a0
    cout<<&v1p<<" "<<&v1p[0]<<" "<<v1p[0]<<endl;    //0x7fff5fbfe260 0x100104380 0x100104370

    return 0;
}
0
kallumama24