Pregunta

Tengo una clase B con un conjunto de constructores y un operador de asignación.

Aquí está:

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

Quiero crear una clase heredada D que simplemente anule la función foo () , y no se requiere ningún otro cambio.

Pero, quiero que D tenga el mismo conjunto de constructores, incluido el constructor de copia y el operador de asignación que B :

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

¿Tengo que reescribirlos todos en D , o hay alguna forma de usar los constructores y operadores de B ? Especialmente me gustaría evitar reescribir el operador de asignación porque tiene que acceder a todas las variables miembro privadas de B .

¿Fue útil?

Solución

Puede llamar explícitamente a constructores y operadores de asignación:

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

Lo interesante es que esto funciona incluso si no definió explícitamente estas funciones (luego usa las funciones generadas por el compilador).

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

Otros consejos

Respuesta corta: Sí, deberá repetir el trabajo en D

Respuesta larga:

Si su clase derivada 'D' no contiene nuevas variables miembro, entonces las versiones predeterminadas (generadas por el compilador deberían funcionar bien). El constructor de copia predeterminado llamará al constructor de copia principal y el operador de asignación predeterminado llamará al operador de asignación principal.

Pero si su clase 'D' contiene recursos, entonces tendrá que trabajar un poco.

Me parece un poco extraño tu constructor de copias:

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

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

Normalmente copie la cadena de constructores para que se copien desde la base hacia arriba. Aquí porque está llamando al operador de asignación, el constructor de copia debe llamar al constructor predeterminado para inicializar el objeto de abajo hacia arriba primero. Luego baja nuevamente utilizando el operador de asignación. Esto parece bastante ineficiente.

Ahora, si realiza una tarea, está copiando de abajo hacia arriba (o de arriba hacia abajo), pero le parece difícil hacerlo y ofrece una garantía de excepción sólida. Si en algún momento un recurso no se copia y arroja una excepción, el objeto estará en un estado indeterminado (que es algo malo).

Normalmente lo he visto al revés.
El operador de asignación se define en términos del constructor de copia y el intercambio. Esto se debe a que hace que sea más fácil proporcionar la garantía de excepción fuerte. No creo que pueda proporcionar una garantía sólida al hacerlo de esta manera (podría estar equivocado).

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

Incluso si deriva una clase D de X esto no afecta este patrón.
Es cierto que debe repetir un poco del trabajo haciendo llamadas explícitas a la clase base, pero esto es relativamente 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 */
    }
};

Lo más probable es que tenga un defecto en su diseño (pista: segmentación , semántica de entidad vs semántica de valor ). Tener una copia completa / semántica de valores en un objeto de una jerarquía polimórfica a menudo no es una necesidad. Si desea proporcionarlo por si uno lo necesita más adelante, significa que nunca lo necesitará. Haga que la clase base no sea copiable (heredando de boost :: noncopdable, por ejemplo), y eso es todo.

Las únicas soluciones correctas cuando tal necesidad realmente aparece son el idioma de la carta envolvente , o el pequeño marco del artículo sobre Objetos regulares por Sean Parent y Alexander Stepanov IIRC. Todas las demás soluciones le darán problemas con el corte y / o el LSP.

Sobre el tema, vea también C ++ CoreReference C.67: C.67: una clase base debería suprimir la copia y, en su lugar, proporcionar un clon virtual si " copiando " se desea .

Deberá redefinir todos los constructores que no sean predeterminados o copiar . No necesita redefinir el constructor de copia ni el operador de asignación, ya que los proporcionados por el compilador (de acuerdo con el estándar) llamarán a todas las versiones 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::=
}

Tenga en cuenta que, como señaló sbi, si define cualquier constructor, el compilador no generará el constructor predeterminado para usted y eso incluye el constructor de copia.

El código original es incorrecto:

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 general, no puede definir el constructor de la copia en términos de la asignación de la copia, porque la asignación de la copia debe liberar los recursos y el constructor de la copia no !!!

Para entender esto, considere:

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

Para evitar pérdidas de memoria, la asignación de copia DEBE eliminar primero la memoria señalada por 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 !!!
}

Entonces, el constructor de copia y la asignación de copia son diferentes porque el primer constructo y el objeto en una memoria inicializada y, el último, DEBEN liberar primero la memoria existente antes de construir el nuevo objeto.

Si hace lo que se sugiere originalmente en este artículo:

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

eliminará una memoria inexistente.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top