¿Cómo usar los constructores y el operador de asignación de la clase base en C ++?

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 heredera D que 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 copias y el operador de asignación como B :

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

¿Tengo que reescribirlos todos en D , o hay una forma de usar los constructores y el operador de B ? Especialmente quisiera evitar reescribir el operador de asignación porque tiene que acceder a todas las variables de miembros privados de B

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 comstackdor).

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

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

Respuesta larga:

Si su clase derivada ‘D’ no contiene nuevas variables miembro, entonces las versiones predeterminadas (generadas por el comstackdor deberían funcionar 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 tu constructor de copia un poco extraño:

 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í, 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 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 (sugerencia: segmentación , semántica de entidad vs semántica de valores ). Tener una semántica completa de copia / valor 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 únicas soluciones correctas cuando tal necesidad realmente aparece son el modismo de la letra envolvente , o el pequeño marco del artículo sobre Objetos regulares de Sean Parent y Alexander Stepanov IIRC. Todas las otras soluciones le darán problemas con el corte y / o el LSP.

Sobre el tema, ver también C ++ CoreReference C.67: C.67: Una clase base debería suprimir la copia, y proporcionar un clon virtual en su lugar si se desea “copiar” .

Tendrá que redefinir todos los constructores que no sean por defecto o copiar constructores. No es necesario redefinir el constructor de copia ni el operador de asignación, ya que los proporcionados por el comstackdor (según el estándar) llamarán a todas las versiones 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 comstackdor 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 copia en términos de la asignación de copia, porque la asignación de copia debe liberar los recursos y el constructor de 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 la pérdida de memoria, la asignación de 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 !!! } 

Por lo tanto, el constructor de copia y la asignación de copia son diferentes porque el antiguo construye y el objeto en una memoria inicializada y, más tarde, DEBE liberar primero la memoria existente antes de construir el nuevo objeto.

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

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

va a eliminar una memoria inexistente.