2008-11-04 28 views
79

¿Cómo puedo usar CRTP en C++ para evitar la sobrecarga de las funciones de miembros virtuales?CRTP para evitar el polimorfismo dinámico

+5

Hola, gracias por todas sus respuestas. Entiendo la mecánica de CRTP. Sin embargo, el problema es que no es realmente polimorfismo, es decir, si tengo X: base {} e Y: base {}, X e Y son tipos no relacionados. es decir, no puedo mantenerlos en el mismo contenedor, ni siquiera usarlos sin conocer el tipo derivado. –

+11

Este _is_ polimorfismo, beit estático: cuando vaya a CRTP, creará un código de cliente con plantilla, que también es (estáticamente) polimorpo: no tiene que cambiar un byte de código para que funcione con otro tipo. – xtofl

Respuesta

1

Tuve que buscar CRTP. Sin embargo, al haber hecho eso, encontré algunas cosas sobre Static Polymorphism. Sospecho que esta es la respuesta a tu pregunta.

Resulta que ATL usa este patrón bastante extensamente.

-3

This La respuesta de Wikipedia tiene todo lo que necesita. A saber:

template <class Derived> struct Base 
{ 
    void interface() 
    { 
     // ... 
     static_cast<Derived*>(this)->implementation(); 
     // ... 
    } 

    static void static_func() 
    { 
     // ... 
     Derived::static_sub_func(); 
     // ... 
    } 
}; 

struct Derived : Base<Derived> 
{ 
    void implementation(); 
    static void static_sub_func(); 
}; 

Aunque no sé cuánto esto realmente te compra. La sobrecarga de una llamada de función virtual es (compilador depende, por supuesto):

  • memoria: Una puntero de función por función virtual
  • Duración: Una función llamada puntero

Mientras que la sobrecarga de CRTP polimorfismo estático es:

  • memoria: Duplicación de Base por instancias de plantilla
  • Duración: Una func Llamada de puntero a la llamada + lo que está haciendo static_cast
+4

En realidad, la duplicación de Base por instanciación de plantilla es una ilusión porque (a menos que aún tenga un vtable) el compilador fusionará el almacenamiento de la base y el derivado en una única estructura para usted.La llamada al puntero a la función también se optimiza mediante el compilador (la parte static_cast). –

+16

Por cierto, su análisis de CRTP es incorrecto. Debería ser: Memoria: Nada, como dijo Dean Michael. Runtime: una llamada a función estática (más rápida), no virtual, que es el objetivo principal del ejercicio. static_cast no hace nada, solo permite compilar el código. –

+2

Mi punto es que el código base se duplicará en todas las instancias de plantilla (la misma fusión de la que hablas). Similar a tener una plantilla con solo un método que se basa en el parámetro de la plantilla; todo lo demás es mejor en una clase base; de ​​lo contrario, se tira ('fusiona') varias veces. – user23167

19

He estado buscando conversaciones decentes sobre CRTP. Todd Veldhuizen's Techniques for Scientific C++ es un gran recurso para esto (1.3) y muchas otras técnicas avanzadas como plantillas de expresión.

Además, descubrí que se podía leer la mayor parte del artículo original de C++ Gems de Coplien en los libros de Google. Tal vez ese sigue siendo el caso.

+0

@fizzer He leído la parte que sugiere, pero todavía no entiendo qué hace la plantilla doble suma (Matrix & A); le compra en comparación con la plantilla double sum (Cualquier & A); –

+0

@AntonDaneyko Cuando se llama en una instancia base, se llama la suma de la clase base, por ejemplo, "área de una forma" con implementación predeterminada como si fuera un cuadrado. El objetivo de CRTP en este caso es resolver la implementación más derivada, "área de un trapezoide "etc., mientras que aún se puede referir al trapezoide como una forma hasta que se requiera un comportamiento derivado. Básicamente, siempre que normalmente se necesite' dynamic_cast' o métodos virtuales. –

120

Hay dos formas.

El primero es mediante la especificación de la interfaz de forma estática para la estructura de tipos:

template <class Derived> 
struct base { 
    void foo() { 
    static_cast<Derived *>(this)->foo(); 
    }; 
}; 

struct my_type : base<my_type> { 
    void foo(); // required to compile. 
}; 

struct your_type : base<your_type> { 
    void foo(); // required to compile. 
}; 

El segundo es al evitar el uso de la referencia-a-base o idioma-puntero a la base y hacer el cableado en tiempo de compilación. Usando la definición anterior, se puede tener funciones de plantilla que se parecen a éstas:

template <class T> // T is deduced at compile-time 
void bar(base<T> & obj) { 
    obj.foo(); // will do static dispatch 
} 

struct not_derived_from_base { }; // notice, not derived from base 

// ... 
my_type my_instance; 
your_type your_instance; 
not_derived_from_base invalid_instance; 
bar(my_instance); // will call my_instance.foo() 
bar(your_instance); // will call your_instance.foo() 
bar(invalid_instance); // compile error, cannot deduce correct overload 

Así que la combinación de la definición de estructura/interfaz y el tipo de deducción en tiempo de compilación en sus funciones le permite hacer el envío estática en lugar de envío dinámico. Esta es la esencia del polimorfismo estático.

+1

buen ejemplo, gracias – ttvd

+14

Excelente respuesta –

+0

Gracias, conseguí su código para usar – Yola