web-dev-qa-db-fra.com

Constructeur de copie implicite C ++ pour une classe qui contient d'autres objets

Je sais que le compilateur fournit parfois un constructeur de copie par défaut si vous ne vous implémentez pas. Je suis confus quant à ce que fait exactement ce constructeur. Si j'ai une classe qui contient d'autres objets, dont aucun n'a un constructeur de copie déclaré, quel sera le comportement? Par exemple, une classe comme celle-ci:

class Foo {
  Bar bar;
};

class Bar {
  int i;
  Baz baz;
};

class Baz {
  int j;
};

Maintenant, si je fais cela:

Foo f1;
Foo f2(f1);

Que fera le constructeur de copie par défaut? Le constructeur de copie généré par le compilateur dans Foo appellera-t-il le constructeur généré par le compilateur dans Bar pour copier sur bar, qui appellera ensuite le constructeur de copie généré par le compilateur dans Baz?

47
jergason
Foo f1;
Foo f2(f1);

Oui, cela fera ce que vous attendez:
Le constructeur de copie f2 Foo :: Foo (Foo const &) est appelé.
Cette copie construit sa classe de base puis chaque membre (récursivement)

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

class X: public Y
{
    private:
        int     m_a;
        char*   m_b;
        Z       m_c;
};

Les méthodes suivantes seront définies par votre compilateur.

  • Constructeur (par défaut) (2 versions)
  • Constructeur (Copier)
  • Destructeur (par défaut)
  • Opérateur d'assignation

Constructeur: Par défaut:

Il existe en fait deux constructeurs par défaut.
Un est utilisé pour zero-initialization tandis que l'autre est utilisé pour value-initialization. L'outil utilisé dépend de l'utilisation de () lors de l'initialisation ou non.

// Zero-Initialization compiler generated constructor
X::X()
    :Y()                // Calls the base constructor
                        //     If this is compiler generated use 
                        //     the `Zero-Initialization version'
    ,m_a(0)             // Default construction of basic PODS zeros them
    ,m_b(0)             // 
    m_c()               // Calls the default constructor of Z
                        //     If this is compiler generated use 
                        //     the `Zero-Initialization version'
{
}

// Value-Initialization compiler generated constructor
X::X()
    :Y()                // Calls the base constructor
                        //     If this is compiler generated use 
                        //     the `Value-Initialization version'
    //,m_a()            // Default construction of basic PODS does nothing
    //,m_b()            // The values are un-initialized.
    m_c()               // Calls the default constructor of Z
                        //     If this is compiler generated use 
                        //     the `Value-Initialization version'
{
}

Remarques: Si la classe de base ou un membre n'a pas de constructeur par défaut visible valide, le constructeur par défaut ne peut pas être généré. Ce n'est pas une erreur sauf si votre code essaie d'utiliser le constructeur par défaut (alors seulement une erreur de compilation).

Constructeur (Copier)

X::X(X const& copy)
    :Y(copy)            // Calls the base copy constructor
    ,m_a(copy.m_a)      // Calls each members copy constructor
    ,m_b(copy.m_b)
    ,m_c(copy.m_c)
{}

Remarques: Si la classe de base ou un membre n'a pas de constructeur de copie visible valide, le constructeur de copie ne peut pas être généré. Ce n'est pas une erreur sauf si votre code essaie d'utiliser le constructeur de copie (alors seulement une erreur de temps de compilation).

Opérateur d'assignation

X& operator=(X const& copy)
{
    Y::operator=(copy); // Calls the base assignment operator
    m_a = copy.m_a;     // Calls each members assignment operator
    m_b = copy.m_b;
    m_c = copy.m_c;

    return *this;
}

Remarques: Si la classe de base ou un membre n'a pas d'opérateur d'affectation viable valide, l'opérateur d'affectation ne peut pas être généré. Ce n'est pas une erreur sauf si votre code essaie d'utiliser l'opérateur d'affectation (alors seulement une erreur de temps de compilation).

Destructeur

X::~X()
{
                        // First runs the destructor code
}
    // This is psudo code.
    // But the equiv of this code happens in every destructor
    m_c.~Z();           // Calls the destructor for each member
    // m_b              // PODs and pointers destructors do nothing
    // m_a          
    ~Y();               // Call the base class destructor
  • Si tout constructeur (y compris copie) est déclaré, le constructeur par défaut n'est pas implémenté par le compilateur.
  • Si le constructeur de copie est déclaré, le compilateur n'en générera pas.
  • Si l'opérateur d'affectation est déclaré, le compilateur n'en générera pas.
  • Si un destructeur est déclaré, le compilateur n'en générera pas.

En regardant votre code, les constructeurs de copie suivants sont générés:

Foo::Foo(Foo const& copy)
    :bar(copy.bar)
{}

Bar::Bar(Bar const& copy)
    :i(copy.i)
    ,baz(copy.baz)
{}

Baz::Baz(Baz const& copy)
    :j(copy.j)
{}
68
Martin York

Le compilateur fournit un constructeur de copie à moins que vous déclariez (note: pas définissez) vous-même. Le constructeur de copie généré par le compilateur appelle simplement le constructeur de copie de chaque membre de la classe (et de chaque classe de base).

Il en va de même pour l'opérateur d'affectation et le destructeur, BTW. C'est différent pour le constructeur par défaut, cependant: cela n'est fourni par le compilateur que si vous ne déclarez aucun autre constructeur vous-même.

11
sbi

Oui, le constructeur de copie généré par le compilateur effectue une copie par membre, dans l'ordre dans lequel les membres sont déclarés dans la classe conteneur. Si l'un des types de membres n'offre pas lui-même de constructeur de copie, le constructeur de copie potentiel de la classe conteneur ne peut pas être généré. Il peut toujours être possible d'en écrire un manuellement, si vous pouvez décider des moyens appropriés pour initialiser la valeur du membre qui ne peut pas être construit par copie - peut-être en utilisant l'un de ses autres constructeurs.

2
seh

Le C++ constructeur de copie par défaut crée une copie superficielle . Une copie superficielle ne créera pas de nouvelles copies d'objets que votre objet d'origine peut référencer; les objets anciens et nouveaux contiendront simplement des pointeurs distincts vers le même emplacement de mémoire.

1
Phil

Le compilateur générera pour vous les constructeurs nécessaires.

Cependant, dès que vous définissez vous-même un constructeur de copie, le compilateur cesse de générer quoi que ce soit pour cette classe et donnera une erreur si vous n'avez pas défini les constructeurs appropriés.

En utilisant votre exemple:

class Baz {
    Baz(const Baz& b) {}
    int j;
};
class Bar {
    int i;
    Baz baz;
};
class Foo {
    Bar bar;
};

Essayer d'instancier par défaut ou de copier-construire Foo générera une erreur car Baz n'est pas constructible par copie et le compilateur ne peut pas générer le constrocteur par défaut et copier pour Foo.

0
Coincoin