5

Necesito usar un puntero de función miembro que tome un argumento de clase base que se usa en otro código. Bueno, simplemente quiero hacer [algo] como el siguiente ejemplo. Este código funciona bien, pero me pregunto si ese lanzamiento siempre es seguro. No puedo hacer dynamic o static emitir aquí.puntero de función de miembro de fundición

#include <cstdio>             

class C 
{               
public:                
     C() : c('c') {}            
     virtual ~C() {}            

     const char c;            
};                 

class D : public C 
{             
public:                
     D() : d('d') {}            
     virtual ~D() {}            

     const char d;            
};                 

class A 
{               
public:                
     A() {}              
     virtual ~A() {}            

     void f(C& c) { printf("%c\n",c.c); }      
     void g(D& d) { printf("%c %c\n",d.c,d.d); }    
};                 

int main (int argc, char const* argv[])        
{                 
     void (A::*pf)(C& c) = &A::f;        
     void (A::*pg)(D& d) = reinterpret_cast<void (A::*)(D&)>(&A::f); 

     A a;               
     C c;               
     D d;               

     (a.*pf)(c);            
     (a.*pg)(d);            

     return 0;             
}                
+3

Dice que el código funciona bien, pero ni siquiera compila para mí. – Xeo

+1

Compila y (parece) trabajar con un 'reinterpret_cast'; Supongo que la pregunta es, ¿es seguro? –

+0

¿Es frío que f y g sean de hecho virtuales? Si es así, puede anularlos en alguna clase derivada de A. Mucho mejor que el casting. –

Respuesta

2

Lo que estás tratando de hacer no se puede hacer legalmente en C++. C++ no admite ningún tipo de co-varianza o contra-varianza en los tipos de parámetros de función, independientemente de si esta es una función miembro o una función libre.

En su situación de la manera adecuada para ponerlo en práctica es introducir una función intermedia para los propósitos de tipo parámetro de conversión de

class A 
{               
public:   
    ...             
    void f(C& c) { printf("%c\n",c.c); }      
    void f_with_D(D& d) { f(d); } 
    ... 
};   

y hacer su punto puntero a esa función intermedia sin ningún tipo de moldes

void (A::*pg)(D& d) = &A::f_with_D; 

Ahora

A a; 
D d;               
(a.*pg)(d); 

en última instancia, ca ll a.f con C subobjeto del objeto d como argumento.

EDIT: Sí, funcionará con sobrecarga de función (si entiendo su pregunta correctamente). Sólo hay que tener en cuenta que con la sobrecarga de la función con el fin de dirigir la llamada interior a la versión correcta de la función que tendrá que usar una conversión explícita

class A 
{               
public:   
    ...             
    void f(C& c) { printf("%c\n",c.c); }      
    void f(D& d) { f(static_cast<C&>(d)); } 
    ... 
};   

Sin el elenco que va a terminar con A::f(D&) llamándose recursivamente.

+0

Desafortunadamente hay muchas clases de Ds y quiero que el compilador elija la adecuada.¿Funcionará con la sobrecarga de funciones? – qba

+0

No, no lo hará. Entonces la conclusión es que necesito una solución completamente diferente. – qba

+0

Funciona, gracias. Actualmente, solo en el código de prueba no hay producción, pero hay una esperanza :) En el código de producción, obtengo 'error: static_cast no válido del tipo '' '. Tal vez hay algo más que está pasando por ahí (static_cast lo hace la biblioteca). – qba

0

Necesita usar un reinterpret_cast para hacerlo funcionar. Debería ser seguro (ver comentarios) en este caso, pero puede fallar si se usa herencia múltiple porque los punteros deberán ajustarse al pasar un D como C. El compilador necesita saber que esto debe suceder, lo que no es posible en este caso (llamar al pg con un d simplemente omitiría este paso, la función miembro obtendría la dirección no modificada de un objeto D).

Observaciones: He dicho seguro - bueno, en realidad es un comportamiento indefinido debido a reinterpret_cast'ing un tipo a un tipo de relación y el uso de ese tipo, pero sin embargo que debería funcionar en la mayoría de los compiladores. Por favor, no lo hagas en el código de producción.

1

El compilador debe rechazar el código con la dynamic_cast que ha escrito. (Creo que fue un error tipográfico y quisiste reinterpretar_cast considerando tu texto de presentación).

Con reinterpret_cast, no se encuentra en uno de los casos bien definidos (que principalmente implica la conversión a otro tipo y luego volver a la original). Así que estamos en el ámbito no especificado para el resultado del elenco, y en el indefinido para el comportamiento al llamar el resultado del elenco.

1

No, su ejemplo no funciona bien.
En primer lugar, solo puede usar dynamic_cast para convertir tipos de clase relacionados, no en otra cosa.
segundo lugar, incluso si se reemplaza dynamic_cast que con un reparto reinterpret_cast o al estilo de C (que supongo que quería decir), entonces me sale el siguiente resultado:

c
c

No es realmente lo que quería.

Por qué incluso funciona y no se cuelga de manera horrible es porque es "seguro" enviar y recibir datos entre los indicadores de función del miembro, no se perderá ninguna información.
Por qué todavía se imprime algo es porque el compilador no ve errores con los tipos, pero al ensamblador no le importan los tipos, solo le importan las direcciones, por lo que seguirá llamando al A::f, porque ese es el puntero que guardó, independientemente del tipo

Curiosamente, esto sigue funcionando incluso si no se relacionan las clases (D no hereda de C), de nuevo porque el ensamblado no se preocupa por los tipos. Cambio de las funciones en A de la siguiente manera:

void f(C& c) { printf("f(C& c): %c\n",c.c); } 
void g(D& d) { printf("g(D& d): %c\n",d.d); } 

conduce al siguiente resultado:

f(C& c): c
f(C& c): d

"¿Cómo funciona eso D ni siquiera tiene un miembro de c!". Bueno, nuevamente por las direcciones. Ambas variables están en el mismo desplazamiento del puntero this, es decir, +0.Ahora vamos a poner otro miembro de C (clase simplificada):

struct C{ 
     C() : c('c') {} 
     int i; // mean 
     const char c; 
}; 

Y intentarlo de nuevo, salida:

f(C& c): c
f(C& c): ╠

Sí, ahí vamos. C::c ahora está en el desplazamiento +4 (+0 + sizeof int), y printf lee desde allí. En D, no existe tal desplazamiento y printf lee de la memoria no inicializada. El acceso a la memoria no inicializada, por otro lado, es comportamiento indefinido.

Entonces, para finalmente llegar a la conclusión: No, esto no es seguro. :)

+0

Bueno, el resultado de reinterpret_cast es lo que realmente quiero. pg es un puntero a la función f, por lo que debe llamar a la función f. ¿Qué más podría ser? – qba

+0

@qba: ¿Entonces por qué lo llamaste 'pg' y tienes una función' g' en tu 'A'? Si desea que se llame a la función 'f', ¿por qué incluyó' g' en su código de ejemplo? – Xeo

+0

Otro ejemplo de lo malo que es copiar y pegar: P Estaba probando C++ con esas funciones, y olvidé eliminar 'g'. – qba

Cuestiones relacionadas