2009-07-06 13 views
22

Consideremos el siguiente código C++:Punteros a funciones de miembros virtuales. ¿Como funciona?

class A 
{ 
public: 
     virtual void f()=0; 
}; 


int main() 
{ 
    void (A::*f)()=&A::f; 
} 

Si tendría que adivinar, diría que & A :: f en este contexto significaría "la dirección de aplicación de la f de A()", ya que no hay una separación explícita entre los punteros a las funciones de miembros regulares y las funciones de miembros virtuales. Y dado que A no implementa f(), eso sería un error de compilación. Sin embargo, no lo es.

Y no solo eso. El siguiente código:

void (A::*f)()=&A::f; 
A *a=new B;   // B is a subclass of A, which implements f() 
(a->*f)(); 

realmente llamará a B :: f.

¿Cómo sucede?

+0

¡Porque el compilador lo hace posible! Si llamar a un método normal no es diferente de llamar a un método virtual, ¿por qué crees que el código es diferente cuando se utilizan punteros de método? ¿A qué crees que el compilador está traduciendo las llamadas al método normal (virtual y ordingary)? –

Respuesta

9

Aquí hay demasiada información acerca de los punteros de función de miembro. Hay algunas cosas sobre las funciones virtuales en "Los compiladores bien comportados", aunque IIRC cuando leí el artículo estaba desdibujando esa parte, ya que el artículo trata realmente de implementar delegados en C++.

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

La respuesta corta es que depende del compilador, pero una posibilidad es que el puntero de función miembro se implementa como una estructura que contiene un puntero a una función "thunk" que hace la llamada virtual.

+0

hola, usted acaba de apuntar sobre thunk. ¿Hay algún buen artículo explicando thunk? – anand

+0

"la palabra thunk hace referencia a un fragmento de código de bajo nivel, generalmente generado por máquina, que implementa algunos detalles de un sistema de software en particular", en http://en.wikipedia.org/wiki/Thunk. Esa página analiza varios tipos de thunks, aunque no este en particular. –

22

Funciona porque el estándar dice que así es como debería suceder. Hice algunas pruebas con GCC, y resulta que para las funciones virtuales, GCC almacena el desplazamiento de la tabla virtual de la función en cuestión, en bytes.

struct A { virtual void f() { } virtual void g() { } }; 
int main() { 
    union insp { 
    void (A::*pf)(); 
    ptrdiff_t pd[2]; 
    }; 
    insp p[] = { { &A::f }, { &A::g } }; 
    std::cout << p[0].pd[0] << " " 
      << p[1].pd[0] << std::endl; 
} 

Eso productos de los programas 1 5 - los desplazamientos de bytes de las entradas de la tabla virtuales de esas dos funciones. Sigue el Itanium C++ ABI, which specifies that.

+0

Supongo que la respuesta a mi pregunta no está normalizada en C++. Sin embargo, también lo son los vtables, pero no conozco ningún compilador que no use vtables como mecanismo para las funciones virtuales, por lo que supongo que también hay un mecanismo * estándar * para esto. Tu respuesta solo me hace confundirme más.Si el compilador almacena 1 y 5 en punteros a las funciones de miembro de A, ¿cómo puede saber si se trata de un índice vtable o una dirección real? (tenga en cuenta que no hay diferencia entre los punteros a las funciones de miembros normales y virtuales) –

+0

¿Por qué esta respuesta lo hace más confundido? Solo pregunta si no hay nada claro. Este tipo de cosas no están estandarizadas. Depende de la implementación pensar en formas de resolverlo. Pueden decidir si es un puntero de función o no: creo que es por eso que agregan 1. Entonces, si el número no está alineado, es un desplazamiento de vtable. Si está alineado, es un puntero a una función miembro. Sin embargo, esto solo es una suposición de mi parte. –

+0

Gracias, eso suena lógico. Sin embargo, algo ineficaz para una implementación en C++ ... Verificó el código en VC, y los resultados son completamente diferentes. La salida es 'c01380 c01390', que parece una dirección de algo. –

1

No estoy del todo seguro, pero creo que es solo un comportamiento polimórfico regular. Creo que &A::f significa realmente la dirección del puntero a la función en el vtable de la clase, y es por eso que no está obteniendo un error de compilación. El espacio en el vtable todavía está asignado, y esa es la ubicación que realmente está recuperando.

Esto tiene sentido porque las clases derivadas esencialmente sobrescriben estos valores con punteros a sus funciones. Esta es la razón por la cual (a->*f)() funciona en su segundo ejemplo: f hace referencia al vtable que se implementa en la clase derivada.

+1

Eso podría haber sido el caso si hubiera una separación entre punteros a funciones de miembros regulares y punteros a funciones de miembros virtuales. Sin embargo, como mencioné, no existe, y de eso se trata. –

+0

Un compilador definitivamente puede poner todos los métodos, virtuales o no en el vtable. Si lo hace, puede usar el índice vtable para los punteros a las funciones miembro. Bastante sencillo para el compilador en realidad - solo asegúrese de que un overrider no virtual obtenga su propia entrada vtable en lugar de sobrescribir la entrada de la clase base. – MSalters

Cuestiones relacionadas