2011-10-28 22 views
10

En un diseño de una jerarquía de clases, estoy usando una clase base abstracta que declara varios métodos que implementarían las clases derivadas. En cierto sentido, la clase base está lo más cerca posible de una interfaz en C++. Sin embargo, hay un problema específico. Considere el siguiente código que declara nuestra clase de interfaz:Tipo abstracto devuelto en la clase base

class Interface { 
public: 
    virtual Interface method() = 0; 
}; 

class Implementation : public Interface { 
public: 
    virtual Implementation method() { /* ... */ } 
}; 

Por supuesto, esto no sería compilar, porque no se puede volver una clase abstracta en C++. Para solucionar este problema que estoy usando la siguiente solución:

template <class T> 
class Interface { 
public: 
    virtual T method() = 0; 
}; 

class Implementation : public Interface<Implementation> { 
public: 
    virtual Implementation method() { /* ... */ } 
}; 

esta solución funciona y es todo lo fino y elegante, sin embargo, para mí, no se ve muy elegante, debido al poco redundante de texto cuál sería el parámetro para la interfaz. Me alegraría si ustedes pudieran señalar nuestros otros problemas técnicos con este diseño, pero esa es mi única preocupación en este momento.

¿Hay alguna manera de deshacerse de ese parámetro de plantilla redundante? Posiblemente usando macros?

Nota: El método en cuestión tiene que devolver una instancia. Soy consciente de que si method() devolviera un puntero o una referencia, no habría ningún problema.

+1

El modismo que está aplicando se denomina [Patrón de plantilla curiosamente recurrente] (http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Curiously_Recurring_Template_Pattern).Supongo que podrías reemplazar la declaración de clase con macro como '#define DERIVE_TEMPLATE_BASE (Derivada, Base) class Derivada: public Base ', pero parece extremadamente feo y probablemente confundirá tu editor. En resumen, sí, hay una redundancia, pero está bien establecida y es un modismo reconocible. – gwiazdorrr

+0

@gwiazdorrr: Eso no parece tan malo. Si es un modismo reconocido, entonces puedo asumir que no será demasiado desconocido para los usuarios de la "interfaz", ¿correcto? – Zeenobit

+1

También tenga en cuenta que (al menos en este ejemplo), no tiene sentido hacer que las llamadas sean virtuales, ya que CRTP requiere que se conozca el tipo más derivado para usar el tipo base. –

Respuesta

5

Interface::method() no se puede devolver una instancia de Interface sin utilizar un puntero o referencia. Devolver una instancia que no sea de puntero Interface requiere instanciar una instancia de Interface, lo que es ilegal porque Interface es abstracto. Si desea que la clase base para devolver una instancia de objeto, usted tiene que utilizar uno de los siguientes:

Un puntero:

class Interface 
{ 
public: 
    virtual Interface* method() = 0; 
}; 

class Implementation : public Interface 
{ 
public: 
    virtual Interface* method() { /* ... */ } 
}; 

Una referencia:

class Interface 
{ 
public: 
    virtual Interface& method() = 0; 
}; 

class Implementation : public Interface 
{ 
public: 
    virtual Interface& method() { /* ... */ } 
}; 

un parámetro de plantilla:

template<type T> 
class Interface 
{ 
public: 
    virtual T method() = 0; 
}; 

class Implementation : public Interface<Implementation> 
{ 
public: 
    virtual Implementation method() { /* ... */ } 
}; 
5

Si bien no puede devolver valor obvio Easons, es completamente en Aceptar para volver punteros o referencias - esto se llama "tipo de retorno covariantes", y es una forma válida de primordial función virtual:

struct Base { virtual Base * foo(); } 
struct Derived : Base { virtual Derived * foo(); } 

El punto es que Derived::foo() es una verdadera anulación , y no una sobrecarga de ocultación de base, porque Derived* es un puntero a una clase derivada de Base. Lo mismo se aplica a las referencias.

En otras palabras, si usted tiene un Base * p, y se llama a p->foo(), siempre se puede tratar el resultado como un puntero a Base (pero si tiene información adicional, como la que su clase es, de hecho, Derived, entonces puede usar esa información).

El orden de composición opuesto, es decir, "tipos de argumentos contravariantes", no está permitido como parte de C++.

Cuestiones relacionadas