Question

J'ai une classe B avec un ensemble de constructeurs et un opérateur d'affectation.

La voici:

class B
{
 public:
  B();
  B(const string& s);
  B(const B& b) { (*this) = b; }
  B& operator=(const B & b);

 private:
  virtual void foo();
  // and other private member variables and functions
};

Je souhaite créer une classe D héritante, qui remplace simplement la fonction foo () . Aucune autre modification n'est requise.

Mais je souhaite que D ait le même ensemble de constructeurs, y compris le constructeur de copie et l'opérateur d'affectation, comme B :

D(const D& d) { (*this) = d; }
D& operator=(const D& d);

Dois-je tous les réécrire dans D ou existe-t-il un moyen d'utiliser les constructeurs et l'opérateur de B ? Je voudrais surtout éviter de réécrire l'opérateur d'affectation car il doit accéder à toutes les variables de membre privées de B .

Était-ce utile?

La solution

Vous pouvez appeler explicitement les constructeurs et les opérateurs d'assignation:

class Base {
//...
public:
    Base(const Base&) { /*...*/ }
    Base& operator=(const Base&) { /*...*/ }
};

class Derived : public Base
{
    int additional_;
public:
    Derived(const Derived& d)
        : Base(d) // dispatch to base copy constructor
        , additional_(d.additional_)
    {
    }

    Derived& operator=(const Derived& d)
    {
        Base::operator=(d);
        additional_ = d.additional_;
        return *this;
    }
};

Ce qui est intéressant, c’est que cela fonctionne même si vous n’avez pas défini explicitement ces fonctions (il utilise ensuite les fonctions générées par le compilateur).

class ImplicitBase { 
    int value_; 
    // No operator=() defined
};

class Derived : public ImplicitBase {
    const char* name_;
public:
    Derived& operator=(const Derived& d)
    {
         ImplicitBase::operator=(d); // Call compiler generated operator=
         name_ = strdup(d.name_);
         return *this;
    }
};  

Autres conseils

Réponse courte: Oui, vous devrez répéter le travail en D

Réponse longue:

Si votre classe dérivée 'D' ne contient aucune nouvelle variable membre, les versions par défaut (générées par le compilateur devraient fonctionner correctement). Le constructeur de copie par défaut appelle le constructeur de copie parent et l'opérateur d'affectation par défaut appelle l'opérateur d'affectation parent.

Mais si votre classe 'D' contient des ressources, vous devrez travailler.

Je trouve votre constructeur de copie un peu étrange:

B(const B& b){(*this) = b;}

D(const D& d){(*this) = d;}

Normalement, copiez la chaîne de constructeurs de sorte qu’ils soient copiés à partir de la base. Ici, parce que vous appelez l’opérateur d’affectation, le constructeur de la copie doit appeler le constructeur par défaut pour initialiser par défaut l’objet de bas en haut. Ensuite, vous redescendez à l'aide de l'opérateur d'affectation. Cela semble plutôt inefficace.

Maintenant, si vous effectuez une tâche que vous copiez de bas en haut (ou de haut en bas), il vous semble difficile de le faire et de fournir une garantie forte en matière d'exceptions. Si à un moment quelconque une ressource ne parvient pas à copier et que vous lâchez une exception, l'objet sera dans un état indéterminé (ce qui est une mauvaise chose).

Normalement, je l’ai vu faire l’inverse.
L'opérateur d'affectation est défini en termes de constructeur de copie et d'échange. En effet, il est plus facile de fournir la garantie d'exception forte. Je ne pense pas que vous serez en mesure de fournir la garantie forte en procédant de cette manière (je peux me tromper).

class X
{
    // If your class has no resources then use the default version.
    // Dynamically allocated memory is a resource.
    // If any members have a constructor that throws then you will need to
    // write your owen version of these to make it exception safe.


    X(X const& copy)
      // Do most of the work here in the initializer list
    { /* Do some Work Here */}

    X& operator=(X const& copy)
    {
        X tmp(copy);      // All resource all allocation happens here.
                          // If this fails the copy will throw an exception 
                          // and 'this' object is unaffected by the exception.
        swap(tmp);
        return *this;
    }
    // swap is usually trivial to implement
    // and you should easily be able to provide the no-throw guarantee.
    void swap(X& s) throws()
    {
        /* Swap all members */
    }
};

Même si vous dérivez une classe D de X, cela n’affecte pas ce modèle.
Certes, vous devez répéter un peu le travail en faisant des appels explicites à la classe de base, mais cela reste relativement simple.

class D: public X
{

    // Note:
    // If D contains no members and only a new version of foo()
    // Then the default version of these will work fine.

    D(D const& copy)
      :X(copy)  // Chain X's copy constructor
      // Do most of D's work here in the initializer list
    { /* More here */}



    D& operator=(D const& copy)
    {
        D tmp(copy);      // All resource all allocation happens here.
                          // If this fails the copy will throw an exception 
                          // and 'this' object is unaffected by the exception.
        swap(tmp);
        return *this;
    }
    // swap is usually trivial to implement
    // and you should easily be able to provide the no-throw guarantee.
    void swap(D& s) throws()
    {
        X::swap(s); // swap the base class members
        /* Swap all D members */
    }
};

Vous avez probablement une faille dans votre conception (indice: slicing , sémantique d'entité vs sémantique de valeur ). Avoir une copie complète / une sémantique de valeur sur un objet issu d'une hiérarchie polymorphe n'est souvent pas une nécessité. Si vous souhaitez le fournir au cas où vous en auriez besoin plus tard, cela signifie que vous n'en aurez jamais besoin. Rendre la classe de base non copiable à la place (en héritant de boost :: noncopyable par exemple), et c'est tout.

Les seules solutions correctes lorsqu'un tel besoin apparaît vraiment sont les idiomes de lettre d'enveloppe ou le petit cadre de l'article sur les Objets normaux . par Sean Parent et Alexander Stepanov IIRC. Toutes les autres solutions vous poseront des problèmes de découpage et / ou de LSP.

Sur le sujet, voir aussi C ++ CoreReference C.67: C.67: Une classe de base doit supprimer la copie et fournir un clone virtuel à la place si "copie" est "copie". est souhaité .

Vous devrez redéfinir tous les constructeurs qui ne sont pas des constructeurs par défaut ou copy . Vous n'avez pas besoin de redéfinir le constructeur de copie ni l'opérateur d'affectation car ceux fournis par le compilateur (selon le standard) appellent toutes les versions de la base:

struct base
{
   base() { std::cout << "base()" << std::endl; }
   base( base const & ) { std::cout << "base(base const &)" << std::endl; }
   base& operator=( base const & ) { std::cout << "base::=" << std::endl; }
};
struct derived : public base
{
   // compiler will generate:
   // derived() : base() {}
   // derived( derived const & d ) : base( d ) {}
   // derived& operator=( derived const & rhs ) {
   //    base::operator=( rhs );
   //    return *this;
   // }
};
int main()
{
   derived d1;      // will printout base()
   derived d2 = d1; // will printout base(base const &)
   d2 = d1;         // will printout base::=
}

Notez que, comme l'a noté sbi, si vous définissez un constructeur, le compilateur ne générera pas le constructeur par défaut pour vous et qui inclut le constructeur de copie.

Le code d'origine est incorrect:

class B
{
public:
    B(const B& b){(*this) = b;} // copy constructor in function of the copy assignment
    B& operator= (const B& b); // copy assignment
 private:
// private member variables and functions
};

En général, vous ne pouvez pas définir le constructeur de copie en termes d'affectation de copie, car l'affectation de copie doit libérer les ressources et le constructeur de copie ne le fait pas !!!

Pour comprendre cela, considérez:

class B
{
public:
    B(Other& ot) : ot_p(new Other(ot)) {}
    B(const B& b) {ot_p = new  Other(*b.ot_p);}
    B& operator= (const B& b);
private:
    Other* ot_p;
};

Pour éviter les fuites de mémoire, l'affectation de copie DOIT d'abord effacer la mémoire désignée par ot_p:

B::B& operator= (const B& b)
{
    delete(ot_p); // <-- This line is the difference between copy constructor and assignment.
    ot_p = new  Other(*b.ot_p);
}
void f(Other& ot, B& b)
{
    B b1(ot); // Here b1 is constructed requesting memory with  new
    b1 = b; // The internal memory used in b1.op_t MUST be deleted first !!!
}

Ainsi, le constructeur de copie et l’affectation de copie sont différents, car la construction et l’objet antérieurs se trouvent dans une mémoire initialisée et, par la suite, DOIT libérer d’abord la mémoire existante avant de construire le nouvel objet.

Si vous faites ce qui est suggéré à l'origine dans cet article:

B(const B& b){(*this) = b;} // copy constructor

vous allez supprimer une mémoire inexistante.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top