2009-08-04 17 views
73

Tengo clase B con un conjunto de constructores y un operador de asignación.¿Cómo usar los constructores y el operador de asignación de la clase base en C++?

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 que hereda D que se acaba de anular la función foo(), y no se requiere ningún otro cambio.

Pero quiero D para tener el mismo conjunto de constructores, incluyendo constructor de copia y operador de asignación como B:

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

¿Tengo que volver a escribir todos ellos en D, o hay una manera de utilizar Constructores y operadores de B? Quisiera especialmente evitar reescribir el operador de asignación porque tiene que acceder a todas las variables de miembro privadas de B.

+0

Si desea sobrescribir el método 'foo', puede usar' using B :: operator =; 'para heredar el operador de asignación, pero los constructores de copia y movimiento no se pueden heredar: https://stackoverflow.com/q/49045026/5447906 –

Respuesta

93

Usted puede llamar explícitamente a los 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 se han definido de forma explícita estas funciones (A continuación, utiliza las funciones del compilador generados).

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

¿Qué significa esto? 'Base (const Base &)' – qed

+1

@CravingSpirit es un [copy constructor] (http://en.wikipedia.org/wiki/Copy_constructor) (con el nombre del argumento omitido). – Motti

+0

Gracias. ¿Por qué necesitamos un constructor de copia si ya hay un operador = sobrecarga? – qed

16

Respuesta corta: Sí tendrá que repetir el trabajo en D

Respuesta larga:

Si su clase 'D' derivado no contiene nuevas variables miembro entonces las versiones predeterminadas (generados por el compilador debe funciona bien). El constructor de copia predeterminado llamará al constructor de copia padre y el operador de asignación predeterminado llamará al operador de asignación primaria.

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

Encuentro su constructor de copia un poco extraño:

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

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

copia Normalmente cadena de constructores para que se construyen a partir de copia de la base hacia arriba. Aquí, como llama al operador de asignación, el constructor de copia debe llamar al constructor predeterminado para inicializar el objeto de abajo hacia arriba. Luego vuelves a bajar usando 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 proporciona una fuerte garantía de excepción. Si en algún momento un recurso no puede copiar y usted lanza una excepción, el objeto estará en un estado indeterminado (lo cual es algo malo).

Normalmente lo he visto hecho al revés.
El operador de asignación se define en términos del constructor de copia y el intercambio. Esto es porque facilita la garantía de excepción fuerte. No creo que pueda proporcionar la 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 obtiene una clase D desde X, esto no afecta a 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 */ 
    } 
}; 
+0

+1. Ya que está en ello, agregue una especialización a std :: swap para su tipo que delegue en su método de miembro de intercambio: 'namespace std {template <> void std :: swap (D & lhs, D & rhs) {lhs.swap (rhs); }} 'De esta forma, la operación de intercambio especializado se puede usar en algoritmos STL. –

+1

Agregar una función de intercambio libre en el mismo espacio de nombres como X * debería * tener el mismo efecto (a través de ADL), pero alguien decía recientemente que MSVC llama incorrectamente a std :: swap explícitamente, haciendo dribeas así ... –

+0

También técnicamente no se les permite agregar cosas al espacio de nombres estándar. –

4

Lo más probable tienen un defecto en su diseño (pista: rebanar, semántica entidad vs semántica de valor). Tener una semántica de valores de copia completa/ en un objeto de una jerarquía polimórfica a menudo no es una necesidad en absoluto. Si desea proporcionarlo solo en caso de que uno lo necesite más adelante, significa que nunca lo necesitará.Haga que la clase base no se pueda copiar en su lugar (heredando de boost :: noncopyable por ejemplo), y eso es todo.

Las soluciones sólo es correcta cuando dicha necesidad realmente aparece son el lenguaje envuelven letras, o el pequeño marco del artículo sobre objetos regulares por Sean Padres y Alexander Stepanov IIRC. Todas las otras soluciones le darán problemas con el corte y/o el LSP.

1

Usted tendrá que redefinir todos los constructores que no son por defecto o copiar constructores. No es necesario redefinir el constructor de copia ni operador de asignación como los proporcionados por el compilador (de acuerdo con el estándar) llamará 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 ha señalado OSE, si se define ningún constructor el compilador no generará el constructor predeterminado para usted y eso incluye el constructor de copia.

+0

Tenga en cuenta que el compilador no proporcionará un ctor predeterminado si se define cualquier otro ctor (esto incluye el ctor de copia). Entonces, si quieres que 'derived' tenga un ctor predeterminado, deberás definirlo explícitamente. – sbi

1

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 se puede definir el constructor de copia en cuanto a la asignación de copia, ya que la asignación copia debe liberar los recursos y el constructor de copia Don ¡¡¡t !!!

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 la pérdida de memoria, la asignación copia primero debe eliminar la memoria apuntada 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 !!! 
} 

Así, constructor de copia y copia de asignación son diferentes porque los primeros construyen y objetan en una memoria inicializada y, más tarde, DEBEN liberar primero la memoria existente antes de construir el nuevo objeto.

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

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

Podrá eliminar una memoria inexistente.

Cuestiones relacionadas