113 votes

Comment utiliser les constructeurs de la classe de base et l'opérateur d'affectation en C ++?

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

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 veux créer une classe qui hérite D qui va juste remplacer la fonction foo(), et aucun autre changement est nécessaire.

Mais je veux D avoir le même ensemble de constructeurs, y compris constructeur de copie et l'opérateur d'affectation en tant que B:

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

Dois-je réécrire tout en D, ou est-il un moyen d'utiliser le B de constructeurs et de l'opérateur? Je tiens tout particulièrement à éviter une réécriture de l'opérateur d'affectation, car il a accès à toutes les B des variables de membre privé.

148voto

Motti Points 32921

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;
    }
};
 

La chose intéressante est que cela fonctionne même si vous n'avez pas explicitement défini 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;
    }
};
 

20voto

Loki Astari Points 116129

Réponse courte: Oui, vous aurez besoin de répéter le travail D

Réponse longue:

Si votre classe dérivée de 'D' ne contient aucun nouveau membre variables, alors la valeur par défaut des versions (généré par le compilateur devrait fonctionner très bien). Le constructeur de Copie par défaut appelez le parent constructeur de copie et l'opérateur d'affectation par défaut appelez le parent opérateur d'affectation.

Mais si votre classe 'D' qui contient des ressources alors vous aurez besoin de faire un peu de travail.

Je trouve votre constructeur de copie un peu étrange:

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

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

Normalement copie constructeurs chaîne de sorte qu'ils sont la copie construit à partir de la base. Ici parce que vous êtes à l'appel de l'opérateur d'affectation le constructeur de copie doit appeler le constructeur par défaut à défaut d'initialiser l'objet de bas en haut en premier. Puis vous descendez à nouveau à l'aide de l'opérateur d'affectation. Cela semble plutôt inefficace.

Maintenant, si vous faites une cession de la copie de bas en haut (ou de haut en bas), mais il semble difficile pour vous de le faire et de fournir une solide exception de garantie. Si, à tout point d'une ressource ne parvient pas à copier et à vous lever une exception, l'objet est dans un état indéterminé (ce qui est une mauvaise chose).

Normalement, je l'ai vu faire dans l'autre sens.
L'opérateur d'affectation est définie par le constructeur de copie et l'échange. C'est parce qu'il est plus facile de fournir la forte exception de garantie. Je ne pense pas que vous serez en mesure de fournir la forte garantie en le faisant de cette manière autour (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 à partir de X cela n'affecte pas ce modèle.
Certes, vous avez besoin de répéter un peu de travail en faisant des appels explicites dans la classe de base, mais c'est relativement trivial.

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 */
    }
};

3voto

Luc Hermitte Points 14171

Vous avez probablement une faille dans votre conception (indice: tranchage, entité sémantique vs valeur sémantique). Avoir une copie intégrale/valeur sémantique sur un objet à partir d'un polymorphe de la hiérarchie est souvent pas nécessaire à tous. Si vous souhaitez fournir juste au cas où, on peut avoir besoin plus tard, cela signifie que vous n'aurez jamais besoin d'elle. Faire de la classe de base non copiable à la place (en héritant de boost::noncopyable par exemple), et c'est tout.

La seule correcte des solutions lorsqu'un tel besoin de vraiment s'affiche sont l' enveloppe-lettre de l'idiome, ou sur le petit cadre de l'article sur les Objets ordinaires par Sean Parent et Alexander Stepanov IIRC. Toutes les autres solutions vous donnera ennuis avec le tranchage, et/ou de la LSP.

2voto

Vous devrez redéfinir tous les constructeurs qui ne sont pas des constructeurs par défaut ou des constructeurs de copie . Vous n'avez pas besoin de redéfinir le constructeur de la 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, ce qui inclut le constructeur de copie.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X