2010-10-07 12 views
15

Tengo problemas con la herencia de operator =. ¿Por qué este código no funciona y cuál es la mejor manera de solucionarlo?Problema con la herencia del operador = en C++

#include <iostream> 

class A 
{ 
public: 
    A & operator=(const A & a) 
    { 
     x = a.x; 
     return *this; 
    } 

    bool operator==(const A & a) 
    { 
     return x == a.x; 
    } 

    virtual int get() = 0; // Abstract 

protected: 
    int x; 
}; 

class B : public A 
{ 
public: 
    B(int x) 
    { 
     this->x = x; 
    } 

    int get() 
    { 
     return x; 
    } 
}; 

class C : public A 
{ 
public: 
    C(int x) 
    { 
     this->x = x; 
    } 

    int get() 
    { 
     return x; 
    } 
}; 

int main() 
{ 
    B b(3); 
    C c(7); 
    printf("B: %d C: %d B==C: %d\n", b.get(), c.get(), b==c); 

    b = c; // compile error 
    // error: no match for 'operator= in 'b = c' 
    // note: candidates are B& B::operator=(const B&) 

    printf("B: %d C: %d B==C: %d\n", b.get(), c.get(), b==c); 
    return 0; 
} 

Respuesta

30

Si no se declara operador de copia-asignación en una clase , el compilador declarará uno para ti implícitamente. El operador de asignación de copia declarado implícitamente ocultará operadores de asignación heredados (lea sobre "nombre oculto" en C++), lo que significa que cualquier operador de asignación heredado se volverá "invisible" al proceso de búsqueda de nombre no calificado (que es lo que sucede cuando lo haga b = c), a menos que tome medidas específicas para "mostrarlos".

En su caso, la clase B no tiene un operador de copia de asignación explícitamente declarado.Lo que significa que el compilador declarará

B& B::operator =(const B&) 

implícitamente. Ocultará el operador heredado de A. La línea

b = c; 

no se compila, ya que, el único candidato aquí es lo anterior implícitamente declarado B::operator = (el compilador le ha hablado de que ya); todos los demás candidatos están ocultos. Y dado que c no es convertible a B&, la asignación anterior no se compila.

Si desea que su código para compilar, puede utilizar mediante declaración-a mostrar la A::operator = heredada mediante la adición de

using A::operator =; 

a la definición de la clase B. El código ahora se compilará, aunque no será un buen estilo. Debe tener en cuenta que en este caso la asignación b = c invocará A::operator =, que asigna solo las porciones A de los objetos involucrados. (Pero al parecer, esa es su intención.)

Como alternativa, en casos como este siempre se puede evitar nombre de ocultación mediante el uso de un cualificado versión del nombre

b.A::operator =(c); 
+0

Cuando dice que lo ocultará, estoy bastante seguro de que el operador de asignación predeterminado asignará la parte "A" utilizando la sobrecarga del usuario. Sin embargo, no le permite asignar nada que derive de una A, que es la razón por la cual el código de OP no se puede compilar. – CashCow

+0

@CashCow: Sí, tienes razón. La forma correcta de expresarlo es que el nombre del operador 'heredado' 'se vuelve invisible para * búsqueda de nombre no calificado *. El compilador, por supuesto, todavía sabe acerca de ese operador y todavía lo usa en otros contextos. – AnT

3

Lo que sucede es que el defecto operator = que el compilador genera para cualquier clase que no tiene uno está ocultando la clase base operator =. En este caso particular, el compilador está generando const B &B::operator =(const B &) detrás de las escenas. Su tarea coincide con este operador e ignora por completo la que declaró en class A. Como un C& no se puede convertir a B&, el compilador genera el error que ve.

Quiere que esto suceda, aunque ahora parece molesto. Impide que funcione el código como has escrito. No desea que código como este funcione porque permite tipos no relacionados (B y C tienen un ancestro común, pero las únicas relaciones importantes en herencia son las relaciones padre-> hijo-> nieto, no las relaciones entre hermanos) que se asignarán a uno otro.

Piénselo desde una perspectiva de ISA. ¿Se debe permitir que un Car se asigne a un Boat solo porque ambos son Vehicles?

Para hacer algo como esto, debe usar el patrón Envelope/Letter. El sobre (también conocido como handle) es una clase especializada cuyo único trabajo es mantener una instancia de alguna clase derivada de una clase base particular (la letra). El controlador reenvía todas las operaciones, pero la asignación al objeto contenido. Para la asignación, simplemente reemplaza la instancia del objeto interno con una copia construida (usando un método 'clon' (aka constructor virtual)) copia del objeto asignado desde.

+0

no es nada descabellado querer copiar un subobjeto la base de una clase derivada a otro. Considera crear una orden a partir de una cotización. No son del mismo tipo, pero comparten una gran cantidad de información sobre el Cliente (nombre/dirección/etc.) y productos pedidos, pero ninguno es un superconjunto verdadero del otro (el pedido no tiene fecha de vencimiento, el presupuesto no tiene Datos de facturación). Por supuesto, la función de modo que esto debería ser una función normal, no 'operator =', y entonces no habría ninguna versión generada por el compilador que lo oculte de las clases derivadas. –

1

No se puede asignar a través de la jerarquía de esta manera: B y C son diferentes subclases de A. Puede asignar una B a una B o una C a una C pero no una C a una B o viceversa.

Probablemente desee implementar operator= en B y C, delegando la parte A de la asignación en A::operator= antes de intentar esto. De lo contrario, las partes específicas de B y C de esas clases se perderán en la tarea.

+0

Esa no es realmente la razón por la que el código está fallando. – Omnifarious

1

Normalmente, el operador = se define en B como

B& operator=(B const &); 

Puesto que B no es una base no ambigua y accesible de 'C', la conversión de C a B no está permitido por el compilador.

Si realmente quiere tener una 'C' se asignará a 'B', 'B' debe soportar un operador de asignación apropiada como

B& operator=(C const &); 
1

(Probablemente no una solución & probablemente no lo que debe hacer) PERO ... hay una manera usted puede forzar la situación si realmente debe:

(A&)(*(&b)) = (A&)(*(&c))