2010-02-11 17 views
5

tengo código como este:C++ ¿Cómo es que no puedo asignar una clase base a una clase para niños?

class Base 
{ 
public: 
    void operator = (const Base& base_) 
    { 
    } 
}; 

class Child : public Base 
{ 
public: 

}; 

void func() 
{ 
    const Base base; 
    Child child; 
    child = base; 
} 

Mi pregunta es: desde niño se deriva de Base (por lo tanto, debe heredar operador de Base =), ¿cómo es que cuando se ejecuta la instrucción

child = base; 

, me conseguir un error del compilador de esta manera:

>.\main.cpp(78) : error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const Base' (or there is no acceptable conversion) 
1>  .\main.cpp(69): could be 'Child &Child::operator =(const Child &)' 
1>  while trying to match the argument list '(Child, const Base)' 

el comportamiento que quiero es para la clase de niño a reconocer que está siendo asignado una clase base, y justo "automáticamente" llama al operador de su padre =.

Una vez que he añadido el código a la clase infantil

void operator = (const Base& base_) 
{ 
    Base::operator=(base_); 
} 

entonces todo compilado bien. Aunque no creo que esto sea bueno porque si tengo como 5 clases diferentes que heredan de Base, entonces tengo que repetir el mismo código en cada clase derivada.

NOTA: Mi intención para copiar el Base a Child es simplemente copiando los miembros que son comunes tanto Base y Child (que sería todos los miembros de Base). Incluso después de leer todas las respuestas a continuación, realmente no veo por qué C++ no permite que uno lo haga, especialmente si hay un operator= explícito definido en la clase Base.

+4

Parece que se está perdiendo los conceptos básicos del polimorfismo. –

+5

Un gato es un animal pero un animal no es necesariamente un gato. – jason

+0

Mi intención era simplemente copiar los miembros que son comunes entre las clases Base y Child. – sivabudh

Respuesta

12

El estándar proporciona el motivo de la pregunta específica de 12,8/10 "objetos de la clase de copia" (énfasis añadido):

Debido a que un operador de asignación de copia se declara implícitamente por una clase si no declarada por el usuario , un operador de asignación de copia de clase base siempre está oculto por el operador de asignación de copia de una clase derivada (13.5.3).

Así que ya que hay un declarado implícitamente operator=(const Child&) cuando el compilador está realizando la resolución/sobrecarga de búsqueda de nombres para un Child::operator=(), firma de la función de la clase Base ni siquiera se considera (que está escondido).

+1

Sí, y como he notado en mi publicación, puede usar "usar" para llevar ese operator = de vuelta al alcance de Childs. (Que me da miedo) –

+1

Me siento como oculto es una mala redacción para el estándar. void Base :: operator = (const Base & base_); y void Chile :: operator = (const Base & base_); son intrínsecamente diferentes ya que uno asigna una Base a una Base y el otro a un Niño. Parece tonto decir que está escondido cuando son funciones diferentes. Debe estar oculto porque no es lógico que exista esa función en la clase Child. – 0xC0DEFACE

+0

El nombre oculto para 'operator =()' se maneja como cualquier otro nombre de función que pueda estar oculto con una gran diferencia: si no declaras 'operator =()' para una clase, obtienes una declaración implícita el nombre oculto está allí, lo quiera o no, y si le hubiera gustado que el nombre no estuviera oculto, no hay mucho que pueda hacer excepto definir 'operator =()' en la clase derivada que lo reenvía a la base implementación de clase Es por eso que el estándar llama el comportamiento de ocultación del nombre específicamente para 'operator =()'. –

0

El problema es que no puede asignar un valor constante a una variable no constante. Si pudiera, podría modificar el valor constante modificando la variable no constante.

¿Es necesario que la base esté const? Lo pregunto porque realmente te encuentras con dos problemas: const correctness y inheritence/polymorphism.

EDITAR: Estoy dudando de mi primera declaración. ¿Alguna aclaración?

+0

Pensé que al principio, pero la asignación es para un niño que no es const, no base (que es const). –

+0

Derecha ... ¿no es ese el problema? ¿Qué sucederá cuando se llame a un método no const en un niño entonces? –

+0

_child_ no es const, por lo que llamar a una función no const es correcta. _base_ es const, pero operator = está tomando _const Base & _ argument así que también está bien. –

2

Porque una base no es un niño. Considere lo siguiente:

class Child : public Base 
{ 
public: 
    SomeFunc(); 
}; 

void func() 
{ 
    const Base base; 
    Child child; 
    child = base; 
    child.SomeFunc();  // whatcha gonna call? 
} 

ACTUALIZACIÓN: Para responder a la pregunta en el comentario, child.SomeFunc(); es perfectamente legal - es sólo un problema si ésimo valor de child no es realmente un objeto secundario. El compilador no puede permitir child = base; porque crearía una situación donde falla la llamada legal.

+0

Este es un buen punto, pero creo que está preguntando por qué está obteniendo el error del compilador. –

1

Cuando no declara explícitamente la asignación de copia, el compilador de C++ creará la asignación de copia para usted. En su caso, sería

class Child : public Base 
{ 
public: 
Child& operator=(const Child& rhs) { ... } 

}; 

Y es por eso que obtiene este error de compilación. Además, tiene más sentido devolver una referencia a sí mismo cuando proporciona una asignación de copia porque le permitirá encadenar al operador.

No es una buena idea declarar una asignación de copia como esta.

void operator = (const Base& base_) 
{ 
    Base::operator=(base_); 
} 

No veo por qué solo quiere copiar la parte de la clase base cuando realiza una asignación de copia.

+0

-1 Esto no tiene nada que ver con el error. –

+0

bien, el error de compilación lo dice claramente. No entiendo tu comentario Él pregunta por qué está obteniendo el error de compilación. –

+1

La sobrecarga del operador de asignación, aunque es la razón por la que el compilador informa, es totalmente ortogonal al problema real aquí, que es que el póster no ha entendido la idea del polimorfismo. Su respuesta es como decirle a la gente que use 'const_cast' para evitar la corrección de const. Podría resolver el error, pero no es el problema real. –

6

No puede asignar Base a Niño porque Child podría no ser la base. Para utilizar el ejemplo típico animal, lo que tenemos aquí es:

const Animal animal; 
Dog dog; 
dog = animal; 

Pero animal podría ser un gato, y que sin duda no puede asignar un gato a un perro.

Dicho esto, no se podía hacerlo a la inversa, ya sea:

const Dog dog; 
Animal animal; 
animal = dog; 

Un perro sin duda es un animal, pero hay un problema de talla aquí. animal ocupa espacio sizeof(Animal) en la pila, pero dog ocupa espacio sizeof(Dog), y esas dos cantidades pueden ser diferentes.

Cuando se trabaja con polimorfismo en C++, debe trabajar con referencias o punteros. Por ejemplo, la siguiente es bien:

Dog* const dog; 
Animal* animal; 
animal = dog; 

En este caso, el tamaño de un puntero es el mismo, independientemente de lo que apunta, por lo que la asignación es posible. Tenga en cuenta que aún necesita mantener las conversiones de jerarquía correctas. Incluso cuando se utiliza un puntero, no se puede asignar una Base a un Niño, por la razón que expliqué anteriormente: Una Base puede ser algo completamente diferente del Niño.

+0

@Poita_: gracias por la excelente respuesta. Sin embargo, tengo una pregunta. Cuando hacemos dog = animal, ¿cómo es que no se puede copiar solo la parte de Animal al perro? ¿Ves lo que quiero decir? p.ej. decir Animal class tiene un miembro privado llamado int numLegs; Entonces, cuando hagamos dog = animal, C++ debería simplemente copiar Animal :: numLegs y asignarlo a Dog :: numLegs. – sivabudh

+0

Es decir, está copiando las partes comunes entre las 2 clases. – sivabudh

+2

Veo lo que quiere decir, pero ¿qué pasaría con las partes del perro que no están especificadas por el Animal? Posiblemente podría diseñar un lenguaje para trabajar de esa manera, pero el problema fundamental aquí es que simplemente no tiene ningún sentido. –

1

La respuesta es más aterradora de lo que pensaba. Usted puede hacer esto. El evens estándar tiene una pequeña nota al respecto en la sección 7.3.3 El principal problema es que el operador = que se define de forma predeterminada en Child oculta el operator = de Base, por lo que debe importarlo de nuevo al ámbito de clase utilizando "using ".

Esto compila OK para mí. Sin embargo, considero que esta idea es muy aterradora y potencialmente plagada de comportamientos indefinidos si se necesita inicializar algo en Child.

class Base 
{ 
public: 
    void operator = (const Base& base_) 
    { 
    } 
}; 

class Child : public Base 
{ 
public: 
using Base::operator=; 

}; 

int main() 
{ 
    const Base base = Base(); 
    Child child; 
    child = base; 
} 

EDITAR: Hizo la base const nuevamente y evitó el error "const no inicializada".

6

El código siguiente es el comportamiento que quería desde el principio, y que compila,

class Base 
{ 
public: 
    void operator = (const Base& base_) 
    { 
    } 
}; 

class Child : public Base 
{ 
}; 

void func() 
{ 
    const Base base; 

    Child child; 

    child.Base::operator=(base); 
} 

nunca supe que se puede llamar explícitamente a algo como:

child.Base::operator=(base); 

De todos modos, he aprendido mucho. Gracias por todas las personas que publicaron respuestas aquí.

+0

Nota: Aprendí la sintaxis anterior al leer http://www.gotw.ca/publications/mill08.htm de Sutter (motivado por el comentario de @Michael Burr) – sivabudh

2

Esto no es realmente una respuesta, pero la pregunta de cómo hacerlo ha sido respondida. Este es el porqué, no.

Las clases deben significar algo. Debería haber cosas útiles que pueda decir sobre cualquier objeto bien formado en la clase ("invariantes de clase"), porque de esa manera puede razonar acerca de los programas sobre la base de cómo funciona una clase. Si una clase es simplemente una colección de miembros de datos, no puede hacer eso, y tiene que razonar sobre el programa en función de cada elemento de datos individual.

El operador de asignación propuesto cambiará todos los miembros de datos en la clase base, pero no tocará los definidos en la clase secundaria. Esto significa una de dos cosas.

Si un objeto secundario está bien formado después de que otro objeto base se vierte en su componente base, significa que los miembros de datos adicionales no tienen una conexión real con los miembros de datos base. En ese caso, Child no es realmente un subtipo de Base, y la herencia pública es la relación incorrecta. El niño no está en una relación "is-a" con Base, sino más bien con una relación "tiene-a". Esto generalmente se expresa por composición (Child tiene un miembro de datos base) y también se puede expresar por herencia privada.

Si un objeto secundario no está bien formado después de que otro objeto base se vierte en su componente base, el operador de asignación propuesto es un método realmente rápido y conveniente para estropear una variable.

Por ejemplo, tengamos una clase base de mamíferos y dos clases secundarias, Cetacean y Bat. El animal tiene cosas como el tamaño y el peso, y las clases de los niños tienen campos relacionados con lo que comen y cómo se mueven. Asignamos un Cetáceo a un Mamífero, lo cual es razonable (todos los campos específicos de Cetáceos se acaban de soltar), y ahora asignamos ese Mamífero a un Murciélago. De repente, tenemos un bate de veinte toneladas.

Con un conjunto de clases bien diseñado, la única forma en que puedo concebir que este operador de asignación siquiera se piense es si las clases secundarias no tienen datos adicionales, solo comportamiento adicional, e incluso entonces es sospechoso.

Por lo tanto, mi único consejo es volver a hacer el diseño de la clase. Muchas personas, particularmente las que están familiarizadas con Java y otros lenguajes que tienden a jerarquías de herencia profunda, usan la herencia demasiado en C++. C++ generalmente funciona mejor con una gran cantidad de clases base y una jerarquía relativamente poco profunda.

Cuestiones relacionadas