2011-08-01 13 views
17

De acuerdo, el título es agotador y creo que probablemente sea por eso que ha sido difícil encontrar una respuesta a través de google o de este sitio. Puede ser que no sepa cómo expresar correctamente el problema, pero aquí va:Coincidencia de una función sobrecargada con su argumento polimórfico

Tengo una serie de métodos en una clase SimpleOpenGLRenderer que toman un único argumento que amplía la clase Model. Entonces, la idea es que, dependiendo del tipo de modelo, el procesador invocará el método correcto que sepa cómo renderizarlo. Aquí es un ejemplo ejecutable simplificado basado en el problema:

#include <stdio.h> 

class Model {}; 

class Cube : public Model {}; 

class Sphere : public Model {}; 

class Renderer 
{ 
    public: 
    virtual void renderModel(const Model& model) = 0; 
}; 

class SimpleOpenGLRenderer 
{ 
    public: 
    void renderModel(const Cube& model) 
    { 
     printf("Render the cube.\n"); 
    } 

    void renderModel(const Model& model) 
    { 
     printf("Throw an exception, my renderer does not support the model type you have provided.\n"); 
    } 

    void renderModel(const Sphere& model) 
    { 
     printf("Render the sphere.\n"); 
    } 
}; 

int 
main(int argc, char** argv) 
{ 
    Cube cube; 
    Model& model = cube; 
    SimpleOpenGLRenderer renderer; 

    renderer.renderModel(cube); 
    renderer.renderModel(model); 
} 

El resultado del ejemplo es:

Render the cube. 
Throw an exception, my renderer does not support the model type you have provided. 

Puede parecer obvio para un desarrollador de C más experimentado ++ que esto no funciona según lo previsto, pero simplemente no tiene sentido para mí. En tiempo de ejecución no sabré el tipo exacto de Model pasado al procesador (de ahí la sobrecarga intentada para resolverlo). Procedente de un fondo Java, he usado esta técnica antes y en Java el método llamado será el que mejor se ajuste al tipo de tiempo de ejecución del argumento. En C++ parece coincidir con el tipo de referencia de tiempo de compilación, incluso si esa referencia puede terminar siendo una subclase que, en mi opinión, se ajusta mejor a otra función.

Hasta ahora, había dado por sentado que este tipo de ejecución coincidía. ¿Simplemente no existe en C++ o lo estoy haciendo de la manera incorrecta? ¿Debo hacer algo diferente en C++ para lograrlo?

Gracias,

Gary.

Respuesta

17

Las sobrecargas en C++ se resuelven en tiempo de compilación, en función del tipo de argumento estático.

Hay una técnica conocida como "doble-despacho" que podrían ser de utilidad:

class Model { 
    virtual void dispatchRender(Renderer &r) const = 0; 
}; 

class Cube : public Model { 
    virtual void dispatchRender(Renderer &r) const { 
     r.renderModel(*this); // type of "this" is const Cube* 
}; 

int main() { 
    Cube cube; 
    Model &model = cube; 
    SimpleOpenGLRenderer renderer; 
    cube.dispatchRender(renderer); 
} 

Tenga en cuenta que la clase Renderer base necesita contener todas las sobrecargas que SimpleOpenGLRenderer hace actualmente. Si desea que sea específico para SimpleOpenGLRenderer qué sobrecargas existen entonces podría poner una función de despacho específica simple en Model, o podría ignorar esta técnica y en su lugar usar dynamic_cast repetidamente en SimpleOpenGLRenderer::renderModel para probar el tipo.

+2

Gracias. No he usado ese patrón antes. Supongo que podría no ser muy común en Java. He hecho algunas pruebas y creo que probablemente recurriré a la solución 'dynamic_cast'. Esperaba algo más elegante, pero el patrón de doble despacho/visitante parece introducir todo tipo de dependencias y estrecha vinculación que no me entusiasman demasiado. –

1

Para la "sobrecarga de tiempo de ejecución" basada en el tipo dinámico, es posible usar visitor pattern.

+0

Ah, gracias por el enlace, lo olvidé. Entonces, ahora, simplemente robó su enlace. Él el. :-) –

2

En su código, las sobrecargas de funciones se resuelven en función del tipo de argumento estático.

Lo que necesita probablemente es el mecanismo double-dispatch que está muy cerca del patrón Visitor. Lea los siguientes:

1

Su código es un buen candidato de tipo de tiempo de ejecución correspondiente, si lo usa. Aquí está recibiendo Cube en Model& y pasando el mismo simplemente al renderModel().Hasta ahora, no le ha dado la oportunidad al compilador de usar el tipo de tiempo de ejecución. Pero más bien confiando en el tipo estático del objeto.

De 2 maneras podrías haber usado la verificación del tipo de tiempo de ejecución. Uno está usando dynamic_cast<> y otro está proporcionando el método de interfaz en Model. es decir

class Model { 
    virtual void print() { printf("throw..."); } // provide an interface method 
}; 

class Cube : public Model { 
    virtual void print() { print("Render cube\n"; } // implement it 
}; 

class Sphere : public Model { 
    virtual void print() { print("Render sphere\n"; } // implement it 
}; 

class SimpleOpenGLRenderer 
{ 
    public: 
    void renderModel(const Model& model) 
    { 
    model.print(); 
    } 
}; 
+0

Creo que usaré la solución 'dynamic_cast' a falta de algo mejor considerando la resolución de tipo estático. No quiero que el 'Model's sea responsable de la representación porque tengo como 10 diferentes' Renderer's que realizan la representación de diferentes maneras. –

0

En C++ la resolución de qué sobrecarga llamar, se realiza en tiempo de compilación.

Para hacer que la implementación efectiva dependa del tipo de argumento polimórfico, debe consultar ese argumento, es decir, llamar a un método virtual en el argumento.

Creo que la forma más limpia de hacerlo aquí es lo que se llama visitor pattern. Su SimpleOpenGLRenderer puede llamar a un método model.renderOn(*this). Entonces Model::renderOn es un conjunto de sobrecargas, una para cada posible tipo de procesador, o es un único método virtual que usa dynamic_cast para descubrir el tipo de procesador. En cualquier caso, entonces llama al en el renderizador, pero ahora esa llamada sabe qué tipo de procesador es y qué tipo es, y también puede llamar a un método de representación muy específico, como SimpleOpenGLRenderer::renderCube.

Saludos,

0

Las otras soluciones que aquí van a hacer exactamente lo que quiere. Pero en mi opinión a costa de la complejidad. Si su problema es exactamente como se describe, le sugiero que cambie la arquitectura de su solución. ¿El renderizador no está tratando de hacer el trabajo del modelo? Lo que veo es una oración de cambio generada por una sobrecarga.

¿Qué tal hacer los modelos de representación de sí mismos, tal vez mediante el uso de algunos métodos de dibujo que ofrece la clase más primitivas:

class Cube : public Model { 
    render(RenderTool& tool) { 
    tool.renderCube(); //or even more low level 
    } 
}; 

class SimpleOpenGLRenderer { 
    public: 
    RenderModel(Model& model) { 
    model.render(renderTool); 
    } 
    private: 
    SomeOpenGLRenderingTool renderTool; 
}; 
+0

No veo que esto sea más simple que los otros métodos presentados. Si estoy usando la "herramienta" para hacer la representación, ¿para qué sirve el "procesador"? Se siente como un intermediario no deseado. No quiero que el 'Modelo' sea responsable de renderizar. La representación se puede lograr de múltiples maneras diferentes e incluso utilizando diferentes API de gráficos. La responsabilidad del 'Modelo' es simplemente describir la geometría. –

+0

Intento adherirme al principio del experto en información. En este caso, el Modelo es el único que sabe cómo es la forma. Activar el tipo para hacer que un objeto externo haga el dibujo es IMO una violación innecesaria de la encapsulación. Para qué sirve el procesador es una pregunta válida. OMI tal vez el modelo debería usar un procesador no al revés. – daramarak

Cuestiones relacionadas