2008-09-29 12 views

Respuesta

19

Simulated dynamic binding. Evitar el costo de llamadas a funciones virtuales mientras se retienen algunos de los beneficios jerárquicos es una gran ganancia para los subsistemas donde se puede hacer en el proyecto en el que estoy trabajando actualmente.

+0

Qué tipo de porcentaje de rendimiento y/o ganancias de uso de memoria obtener, por cierto? –

+0

He visto disminuir el tiempo invertido en un cálculo en un orden de magnitud. Este fue, sin duda, un caso de esquina muy inusual, generalmente es mucho menos dramático. Las ganancias de uso de memoria son una proporción insignificante del uso general de la memoria para nosotros. – moonshadow

+1

No veo cómo se puede usar. Polymorhpishm funciona utilizando un puntero de clase base para llamar a los métodos de clase derivados según el tipo de clase derivada. Sin embargo en el código CRTP todavía tiene que plantilla base de clase { pública: vacío impl() { static_cast (este) -> impl(); } }; clase derivada: base pública < derived > { void impl() { /* algo */ } }; base < derived *> basePtr; Así que todavía tiene que mencionar la clase derivada en la plantilla que derrota el aspecto polimórfico ya que he tenido que indicar explícitamente la clase derivada – ScaryAardvark

1

Generalmente se usa para patrones de tipo polimórfico en los que no es necesario que pueda elegir la clase derivada en tiempo de ejecución, solo en tiempo de compilación. Esto puede ahorrar la sobrecarga de la llamada de función virtual en tiempo de ejecución.

+0

@Greg: ¿puedes dar un ejemplo de código? before: [[clase B {virtual void f() = 0; void g() {.. f(); ...}}; clase D: B {void f();};]] después de [[plantilla clase B {void g() {static_cast (esto) -> f();}}; clase D: B {void f();};]] etc., pero elaborado, quizás con más tipos de ejemplos relatables. – Aaron

-1

Parece una especie de C macro: aprovecha que la macro no se compila en el momento de la definición, sino en el momento del uso.

#define CALL_THE_RIGHT_FOO foo() 

archivo A:

static void foo() { 
    // do file A thing 
} 
... 
CALL_THE_RIGHT_FOO 
... 

archivo A:

static void foo() { 
    // do file B thing 
} 
... 
CALL_THE_RIGHT_FOO 
... 

El patrón de uso de la plantilla que estás describiendo nos permite hacer "llamamos a la derecha foo" en la plantilla padre, posponiendo la definición de qué exactamente es el foo correcto hasta que se crea una instancia de la plantilla. Excepto en este caso, es la distinción entre ClassA :: foo y ClassB :: foo en función del valor de T en Parent.

19

También es especialmente útil para mixins (me refiero a las clases heredadas para proporcionar funcionalidad) que ellos mismos necesitan saber en qué tipo están operando (y por lo tanto deben ser plantillas).

En efectiva C++, Scott Meyers proporciona como un ejemplo una plantilla de clase NewHandlerSupport <T>. Esto contiene un método estático para anular el nuevo controlador para una clase en particular (de la misma manera que std :: set_new_handler lo hace para el operador predeterminado new) y un operador nuevo que usa el controlador. Para proporcionar un manejador por tipo, la clase padre necesita saber en qué tipo está actuando, por lo que debe ser una plantilla de clase. El parámetro de la plantilla es la clase hija.

que realmente no podía hacer esto sin CRTP, ya que se necesita la plantilla NewHandlerSupport deberían ejecutarse por separado, con un miembro de datos estática independiente para almacenar el new_handler actual, por cada clase que lo utiliza.

Obviamente, el ejemplo completo es extremadamente no seguro para subprocesos, pero ilustra el punto.

Meyers sugiere que se podría pensar en CRTP como "Hágalo por mí". Yo diría que este es generalmente el caso para cualquier mixin, y CRTP se aplica en el caso donde se necesita una plantilla mixin en lugar de solo una clase mixin.

4

El CRTP se vuelve mucho menos curioso si se tiene en cuenta que el tipo de subclase que se pasa a la superclase solo se necesita en el momento de la expansión del método. Entonces todos los tipos están definidos. Solo necesita que el patrón importe el tipo de subclase simbólica en la superclase, pero es solo una declaración directa (como todos los tipos de parámetros formales formales son por definición) en lo que se refiere a la superclase.

Utilizamos en una forma algo modificada, pasando la subclase en una estructura de tipos de rasgos a la superclase para hacer posible que la superclase devuelva objetos del tipo derivado. La aplicación es una biblioteca de cálculo geométrico (puntos, vectores, líneas, recuadros) donde toda la funcionalidad genérica se implementa en la superclase, y la subclase solo define un tipo específico: CFltPoint hereda de TGenPoint. También CFltPoint existía antes de TGenPoint, por lo que la creación de subclases era una forma natural de refactorizar esto.

+0

@QBizZ: ¿Sabe que esto también podría implementarse mediante funciones virtuales? Una función virtual que devuelve un puntero a una clase base puede devolver un puntero a una clase derivada dentro de la clase derivada –

+0

¿Está hablando de tipos de devolución co-variante? ¿Dónde una anulación de método virtual puede devolver un puntero (o referencia) a una clase derivada del tipo de retorno original en la clase base? Si es así, entonces soy consciente de esto. Pero para mí es algo diferente ya que la interfaz define el contrato con la clase base. No hay ningún método (o cualquier otro identificador) al que pueda acceder la clase base que no se haya visto en alguna definición. Mientras está en el CRTP, la clase base puede llamar a métodos en una subclase que ni siquiera se han definido en el punto de llamada. Esto sucede en el punto de ejemplificación. – QBziZ

1

Para un uso de biblioteca en el mundo real de CRTP, mire ATL y WTL (wtl.sf.net). Se usa ampliamente allí para el polimorfismo en tiempo de compilación.

Cuestiones relacionadas