2010-12-09 12 views
7

La mayoría de las discusiones que he visto sobre los punteros de miembro se centran en las conversiones permitidas en el tipo al que pertenece el miembro. Mi pregunta es acerca de las conversiones sobre el tipo de miembro.Conversión implícita de puntero a miembro de datos frente a no miembro

struct Base{}; 
struct Derived : public Base{}; 
struct Foo{ Derived m_Derived; }; 

Teniendo en cuenta estas declaraciones, el siguiente código produce un error (MSVC 2008):

// error C2440: 'initializing' : cannot convert from 'Derived Foo::* ' to 'Base Foo::* ' 
Base Foo::*p = &Foo::m_Derived; 

La conversión de derivados * * basar normalmente se permitió - por qué la diferencia aquí?

+1

"La conversión de Base * a Derivado * normalmente está permitida" Creo que tiene esto al revés. –

+0

Buena captura. Fijo. – Chris

+0

Ver http://stackoverflow.com/questions/4295117/pointer-to-member-conversion – icecrime

Respuesta

0

Interesante pregunta. Los punteros de datos se usan tan poco, no estoy familiarizado con las reglas.

Sin embargo, voy a poner dinero que es debido a la herencia múltiple. Si Base y Derivados usan herencia virtual, el compilador no puede saber en tiempo de compilación el desplazamiento de Base dentro de un Derivado dado, haciendo imposible una compensación en tiempo de compilación, y sería demasiado trabajo legalizarlo para no virtual. herencia.

+0

pero el mismo argumento funciona en contra de permitir conversiones para los punteros que no son miembros, doesn ' ¿Es eso? – lijie

+0

@lijie: No, porque esas conversiones ocurren en tiempo de ejecución. Esta conversión efectivamente está sucediendo en tiempo de compilación. – Puppy

+0

¿Qué es "esta conversión"? – lijie

2

Tiene la varianza hacia atrás.

Los tipos de devolución convierten implícitamente a tipos base (contravarianza). Pero los parámetros se convierten implícitamente en tipos derivados (covarianza) y el tipo de clase en un puntero a miembro actúa como un parámetro . Para ver esto, apliquemos el Principio de Sustituibilidad de Liskov:

El contrato de Base* es: "Le daré una Base" (cuando usa el operador * en mí). El contrato de Derived* es "Le daré un derivado, que también es una base".

Claramente se puede usar Derived* en lugar de Base*. Por lo tanto, hay una conversión implícita de Derived* a Base*.

Pero considere el contrato de un puntero a miembro.

El contrato de int Base::* es: "Dame una base y Yo te daré la espalda un int" (Un derivado es una base, por lo que aquellos son bien también) El contrato de int Derived::* es: "Dame un derivado, y yo te devuelvo un int"(pero no cualquier edad Base va a hacer, que debe ser un Derived)

Imagine que tiene un Base que no es un Derived. Funcionará muy bien cuando elimine la referencia de un int Base::*, pero no se puede usar con un int Derived*).

Sin embargo, si usted tiene un Derived, que se puede utilizar para eliminar la referencia tanto int Base::* y int Derived::*. Por lo tanto hay una conversión implícita de int Base::* a int Derived::*

Argh, hice lo que usted ha dicho y analizado el tipo que pertenece el miembro.

El LSP aún funciona. Y acepto que la conversión sea legal, al menos de acuerdo con la seguridad del tipo.El contrato es "Dame un Foo y te daré un Derived", que claramente puedes usar para obtener desde Foo hasta Base computándolo con una conversión implícita. Entonces es seguro. DeadMG probablemente esté en el camino correcto señalando la posible complicación con la ubicación de la relación del subobjeto base, especialmente en herencia virtual. Pero los punteros con los miembros tratan estos problemas en el LHS del operador de desreferencia, por lo que también podrían hacerlo por el resultado.

La respuesta final es, probablemente, simplemente que la norma no requiere que la conversión sea legal.

+0

Extrañamente, 'Derived * (Foo :: *)' _can_ se puede convertir a 'Base * (Foo :: *)' (por comeau). Me pregunto si esto es una "extensión". – lijie

1

@Ben Voigt: ¿Por qué acertó su respuesta correcta?

Las conversiones de puntero seguro son contra variantes, lo que significa que puede subir de forma segura pero no descendente.

Las conversiones seguras del puntero a miembro son covariantes, lo que significa que puede descender de forma segura, pero no subir.

La razón es fácil de ver cuando lo piensas. Supongamos que tiene un puntero a un miembro de Base, eso es una compensación en Base. Entonces, si el objeto completo es un Derivado, ¿cuál es el desplazamiento de ese mismo miembro en Derivado? Por lo general, es exactamente el mismo desplazamiento: sin duda lo será si los punteros a Base y Derivados fueran direcciones iguales. Si tiene herencia múltiple y bases virtuales, las cosas se vuelven un poco complicadas :)

De hecho, el código de ejemplo del OP es el ejemplo perfecto de por qué los cambios no son seguros: el desplazamiento de un miembro de la clase derivada podría aplicarse a cualquier Base, y apuntar al final del subobjeto base, que solo está bien si en realidad es un Derived y no dice, un Derived2.

+1

Pero no estamos tratando de actualizar la clase del puntero de datos, estamos tratando de apuntar un puntero de datos a un upcast de su tipo (me faltan nombres aquí ...). Estas no son la misma cosa. – Hexagon

+0

No hay nada que diga que un puntero debe ser un simple desplazamiento, y además, la pregunta de OP no se trata de un "puntero a un miembro de Base". Se trata de un "puntero a un miembro de Foo" con el tipo de miembro como Base. Y el compilador _does_ tiene en cuenta la conversión de un "puntero a un miembro de A" en un "puntero a un miembro de B" donde A deriva de B. – lijie

+0

Porque mi respuesta original, como la tuya, habla sobre los miembros de 'Derived' y miembros de 'Base'.Pero la pregunta es si los miembros escribieron 'Base' o si escribieron' Derived', pero todos son miembros ** de ** 'Foo'. –

Cuestiones relacionadas