2011-05-28 12 views
8

Siempre pienso que conozco C++ bastante bien, pero a veces me sorprenden incluso las cosas más fundamentales.Confusión de constructores

En el siguiente escenario, estoy confundido en cuanto a por qué el constructor Derived::Derived(const Base&) se invoca:

class Base 
{ }; 

class Derived : public Base 
{ 
    public: 

    Derived() { } 

    Derived(const Base& b) 
    { 
     std::cout << "Called Derived::Derived(const Base& b)" << std::endl; 
    } 
}; 

int main() 
{ 
    Derived d; 
    Base b; 
    d = b; 
} 

Este salidas: Called Derived::Derived(const Base& b), lo que indica que el segundo constructor en Derived se invocó. Ahora, pensé que conocía C++ bastante bien, pero no puedo entender por qué se invocaría ese constructor. Comprendo todo el concepto de "regla de cuatro", y creo que la expresión d = b haría una de dos cosas: o bien 1) invocaría el operador de asignación implícita (generado por el compilador) de Base, o 2) activaría un compilador error al quejarse de que la función Derived& operator = (const Base&) no existe.

su lugar, se llama un constructor , a pesar de que la expresión d = b es una expresión de asignación.

Entonces, ¿por qué sucede esto?

+0

Quizás agregue un operador de asignación de copia y una declaración de registro. ;) – Xeo

+0

La respuesta que eligió olvidó mencionar que esto sucede porque definió el constructor Derived (const Base &) y está realizando un downcast ... si no definió este construtor, obtendría un error de compilación al intentar realizar la tarea como se indica en mi respuesta. – Hazok

+0

@Zach: el OP es el que elige, no se preocupe, se le han notificado todas las respuestas (y continuará recibiendo notificaciones). Si tu respuesta es excelente, los lectores cuidadosos lo subirán de todos modos y pronto se unirán a las respuestas principales y serán visibles para futuros lectores (menos cuidadosos) :) –

Respuesta

11

d = b puede suceder porque b se convierte en Derivado. El segundo constructor se utiliza para la conversión automática de tipos. Es como d = (Derivado) b

Derivado de una base, pero la base no es derivada, por lo que debe convertirse antes de la asignación.

+0

No dije que es un constructor de copia. Es un constructor que crea un derivado de una base. Lo mismo que quieres en un molde. –

+0

Descartar el comentario anterior, me olvidé de las reglas y secuencias de conversión implícitas que se aplican. – Xeo

6

asignando base a derivada? quizás quisiste decir (a) por ref (b) o derivar a base. Esto realmente no tiene sentido, pero el compilador usa correctamente su constructor (no explícito) para convertir la instancia Base a una nueva instancia derivada (que luego se asigna a d).

Utilice un constructor explicut para evitar que esto suceda automáticamente.

personalmente pienso en mal estado a su ejemplo de código, ya que, normalmente la asignación de base de primera clase de derivados que no tiene sentido sin una conversión

+0

+1 por la recomendación de 'explicita' para resolver el problema. Todos los constructores deben declararse como "explícitos", a menos que haya una buena razón para no hacerlo (si desea una conversión implícita). –

+0

Todos los constructores no necesariamente deben declararse explícitamente, especialmente en el caso de constructores vacíos. Muchos estándares de codificación en toda la industria especifican usar los constructores por defecto en lugar de los constructores vacíos. – Hazok

+0

@Zach: hace tiempo que me molesta ... ¿No entiendes lo que quieres decir con * empty * constructor? Hubiera pensado que querías decir constructores por defecto (es decir, lista de argumentos vacía) pero como te opones directamente a los dos términos, no entiendo lo que quieres decir: / –

1

No se puede asignar un valor de tipo diferente, por lo que primero debe construir un Derived temporal .

2

Puesto que usted ha definido un constructor para que tenga Derivado tipo de base y que está abajo-bastidor bajo, el compilador elige el constructor más adecuado para la conversión hacia arriba, que en este caso es la Dervied (const Base & b) se ha definido. Si no definió este constructor, en realidad obtendría un error de compilación al intentar realizar la asignación. Para obtener más información, puede leer lo siguiente al Linuxtopia.

5

Hay dos características que interactúan en juego aquí:

  • Operadores de asignación no se heredan
  • un constructor que no es explícita, o un operador de conversión (operator T()) definir un usuario de conversión que puede ser utilizado implícitamente como parte de una secuencia de conversión

Operadores ASIGNACIÓN Nunca se heredan

Un simple ejemplo de código:

struct Base {}; // implicitly declares operator=(Base const&); 
struct Derived: Base {}; // implicitly declares operator=(Derived const&); 

int main() { 
    Derived d; 
    Base b; 
    d = b; // fails 
} 

De ideone:

prog.cpp: In function ‘int main()’: 
prog.cpp:7: error: no match for ‘operator=’ in ‘d = b’ 
prog.cpp:2: note: candidates are: Derived& Derived::operator=(const Derived&) 

secuencia de conversión

Siempre que existe un desfase de "impedancia", tal como aquí:

  • Derived::operator= espera un argumento Derived const&
  • Base& se proporciona un

el compilador tratará de establecer una secuencia de conversión para cerrar la brecha. Tal secuencia de conversión puede contener al más una conversión definida por el usuario.

Aquí, se buscará:

  • cualquier constructor de Derived que puede ser invocado con un Base& (no explícito)
  • un operador de conversión en Base que produciría un elemento Derived

No hay Base::operator Derived() pero hay un constructor Derived::Derived(Base const&).

Por lo tanto nuestra secuencia conversión se define para nosotros:

  • Base&
  • Base const& (trivial)
  • Derived (usando Derived::Derived(Base const&))
  • Derived const& (objeto temporal unido a una referencia const)

A nd entonces se llama Derived::operator(Derived const&).

En la acción

Si aumentamos el código con algunos rastros más, podemos see it in action.

#include <iostream> 

struct Base {}; // implicitly declares Base& operator(Base const&); 
struct Derived: Base { 
    Derived() {} 
    Derived(Base const&) { std::cout << "Derived::Derived(Base const&)\n"; } 
    Derived& operator=(Derived const&) { 
    std::cout << "Derived::operator=(Derived const&)\n"; 
    return *this; 
    } 
}; 

int main() { 
    Derived d; 
    Base b; 
    d = b; 
} 

que da salida:

Derived::Derived(Base const&) 
Derived::operator=(Derived const&) 

Nota: Prevención de esto?

Es posible, en C++, eliminar un constructor para ser utilizado en las secuencias de conversión. Para hacerlo, uno debe anteponer la declaración del constructor utilizando la palabra clave explicit.

En C++ 0x, también es posible utilizar esta palabra clave en operadores de conversión (operator T()).

Si aquí usamos explicit antes de Derived::Derived(Base const&), el código se convierte en mal formado y debe ser rechazado por el compilador.