2009-08-14 14 views

Respuesta

10

Respuesta corta: las funciones virtuales consisten en no saber quién llamó hasta en el tiempo de ejecución, cuando se selecciona una función de un conjunto ya compilado de funciones candidatas. Las plantillas de función, OTOH, consisten en crear un número arbitrario de funciones diferentes (utilizando tipos que quizás ni siquiera se conocían cuando se escribió el destinatario) en tiempo de compilación desde el lado de las personas que llaman. Eso simplemente no coincide.

Respuesta algo más larga: Las funciones virtuales se implementan utilizando un direccionamiento indirecto general (el Cursor general multiuso del programador), generalmente implementado como una tabla de punteros a función (la llamada tabla de función virtual, a menudo abreviada "vtable"). Si está llamando a una función virtual, el sistema en tiempo de ejecución elegirá la función correcta de la tabla. Si hubiera plantillas de funciones virtuales, el sistema en tiempo de ejecución tendría que encontrar la dirección de una instancia de plantilla ya compilada con los parámetros de plantilla exactos. Como el diseñador de la clase no puede proporcionar un número arbitrario de instancias de plantillas de funciones creadas a partir de un conjunto ilimitado de posibles argumentos, esto no puede funcionar.

9

¿Cómo construirías el vtable? En teoría, podría tener un número infinito de versiones de su miembro con plantilla y el compilador no sabría qué podría ser cuando crea el vtable.

+0

¿Hay alguna posibilidad de que la persona que recientemente votó negativamente por esta sea tal vez lo suficientemente valiente como para decir por qué? :) – Troubadour

+0

Vtable es un detalle de implementación y las dificultades en los back-ends no deben tenerse en cuenta cuando se diseñan funciones de lenguaje, al menos, 'C++'. –

+1

Pavel: Perdón por decírtelo, pero es por eso que está prohibido. Intenta leer el "Lenguaje de programación C++" de Stroustrup si no me crees. – Troubadour

1

Creo que es para que los compiladores puedan generar compensaciones vtable como constantes (mientras que las referencias a funciones no virtuales son correcciones).

Cuando compila una llamada a una función de plantilla, el compilador simplemente pone una nota en el binario, diciéndole efectivamente al vinculador "por favor reemplace esta nota con un puntero a la función correcta". El vinculador estático hace algo similar y, finalmente, el cargador rellena el valor una vez que el código se ha cargado en la memoria y se conoce su dirección. Esto se conoce como corrección, porque el cargador "arregla" el código completando los números que necesita. Tenga en cuenta que para generar la corrección, el compilador no necesita saber qué otras funciones existen en la clase, solo necesita saber el nombre de la función que quiere.

Sin embargo, con las funciones virtuales, el compilador normalmente emite un código que dice "saca el puntero vtable del objeto, agrégalo a 24, carga una dirección de función y llámalo". Para saber que la función virtual particular que desea está en el desplazamiento 24, el compilador necesita conocer todas las funciones virtuales de la clase y el orden en que aparecerán en la tabla. Tal como están las cosas, el compilador lo sabe, porque todas las funciones virtuales están listadas allí en la definición de la clase. Pero para generar una llamada virtual donde haya funciones virtuales con plantilla, el compilador necesitaría saber, en el momento de la llamada, qué instancias hay de la plantilla de función. No puede saber esto, porque las diferentes unidades de compilación pueden instanciar diferentes versiones de una plantilla de función. Por lo tanto, no se pudo determinar qué compensación usar en el vtable.

Ahora, sospecho que un compilador podría soportar plantillas de funciones virtuales al emitir, en lugar de una compensación de vtable constante, una corrección de enteros. Es decir, una nota que dice "complete el desplazamiento de vtable de la función virtual con este nombre contaminado". Entonces, el enlazador estático podría completar el valor real una vez que sepa qué instancias están disponibles (en el punto en que elimina las instancias de plantillas duplicadas en diferentes unidades de compilación). Pero eso impondría una gran carga de trabajo en el enlazador para descubrir diseños vtable, que actualmente el compilador hace por sí solo. Las plantillas se especificaron deliberadamente para facilitar las cosas a los implementadores, con la esperanza de que en realidad aparezcan en la naturaleza algún tiempo antes de C++ 0x ...

Por lo tanto, supongo que algunos razonamientos en este sentido llevaron al comité de estándares a concluir que las plantillas de funciones virtuales, incluso si eran implementables, eran demasiado difíciles de implementar y por lo tanto no podían incluirse en el estándar.

Tenga en cuenta que hay un poco de especulación en lo anterior incluso antes de tratar de leer las mentes del comité: no soy el autor de una implementación de C++, y tampoco toco una en televisión.

4

Las otras respuestas ya han mencionado que las funciones virtuales se manejan generalmente en C++ al tener en el objeto un puntero (vptr) a una tabla. Esta tabla (vtable) contiene un puntero a las funciones que se utilizarán para los miembros virtuales, así como algunas otras cosas.

La otra parte de la explicación es que las plantillas se manejan en C++ mediante la expansión de código. Esto permite una especialización explícita.

Ahora, algunos idiomas requieren (Eiffel - Creo que también es el caso de Java y C#, pero mi conocimiento de ellos no es lo suficientemente bueno para ser autoritario) o permitir (Ada) un manejo compartido de la genérica, don No tiene especialización explícita, pero permitiría la función de plantilla virtual, colocar la plantilla en las bibliotecas y podría reducir el tamaño del código.

Puede obtener el efecto de genericidad compartida mediante el uso de una técnica llamada borrado de tipo. Esto está haciendo de forma manual lo que están haciendo los compiladores para el lenguaje genérico compartido (bueno, al menos algunos de ellos, dependiendo del idioma, podrían ser posibles otras técnicas de implementación). Aquí hay un ejemplo (tonto):

#include <string.h> 
#include <iostream> 

#ifdef NOT_CPP 
class C 
{ 
public: 
    virtual template<typename T> int getAnInt(T const& v) { 
     return getint(v); 
    } 
}; 
#else 
class IntGetterBase 
{ 
public: 
    virtual int getTheInt() const = 0; 
}; 

template<typename T> 
class IntGetter: public IntGetterBase 
{ 
public: 
    IntGetter(T const& value) : myValue(value) {} 
    virtual int getTheInt() const 
    { 
     return getint(myValue); 
    } 
private: 
    T const& myValue; 
}; 

template<typename T> 
IntGetter<T> makeIntGetter(T const& value) 
{ 
    return IntGetter<T>(value); 
} 

class C 
{ 
public: 
    virtual int getAnInt(IntGetterBase const& v) 
    { 
     return v.getTheInt(); 
    } 
}; 
#endif 

int getint(double d) 
{ 
    return static_cast<int>(d); 
} 

int getint(char const* s) 
{ 
    return strlen(s); 
} 

int main() 
{ 
    C c; 
    std::cout << c.getAnInt(makeIntGetter(3.141)) + c.getAnInt(makeIntGetter("foo")) << '\n'; 
    return 0; 
} 
+1

+1 por mencionar el borrado de tipo – sbi

Cuestiones relacionadas