2009-08-11 25 views
27

This question me preguntaron aquí hace unas horas y me hicieron darme cuenta de que nunca he usado realmente los tipos de retorno covariantes en mi propio código. Para aquellos no está seguro de qué es la covarianza, está permitiendo que el tipo de devolución de (típicamente) las funciones virtuales difieran siempre que los tipos sean parte de la misma jerarquía de herencia . Por ejemplo:¿Cuándo es la covarianza de C++ la mejor solución?

struct A { 
    virtual ~A(); 
    virtual A * f(); 
    ... 
}; 

struct B : public A { 
    virtual B * f(); 
    ... 
}; 

Se dice que los diferentes tipos de retorno de las dos funciones f() son covariantes. Las versiones anteriores de C++ necesarios los tipos de retorno a ser el mismo, así que B tendría que quedar así:

struct B : public A { 
    virtual A * f(); 
    ... 
}; 

Por lo tanto, mi pregunta: ¿Alguien tiene un ejemplo del mundo real, donde covariante regresan se requieren tipos de funciones virtuales , o producir una solución superior para simplemente devolver un puntero base o referencia?

+0

Cuando leí la pregunta, mi reacción fue exactamente la misma. ¿Cuál es el problema? ¡No use tipos de retorno covariantes! –

Respuesta

27

El ejemplo canónico es un método .clone()/.copy(). Por lo tanto, siempre puede hacer

obj = obj->copy(); 

independientemente del tipo de obj.

Editar: Este método de clonación se definiría en la clase base del objeto (como lo es en realidad en Java). Por lo tanto, si el clon no era covariante, tendrías que realizar el colado, o se restringiría a los métodos de la clase base raíz (que tendría muy pocos métodos, en comparación con la clase del objeto fuente de la copia).

+2

Pero a usted (IMHO) no le debe importar cuál es el tipo A * a = some_a_or_b-> clone(); funciona igual de bien y luego utiliza otros métodos virtuales en el puntero clonado. –

+0

Veo la covarianza más como azúcar sintáctico, luego como una característica necesaria. Siempre puedes hacer A * a = b-> clone(); dynamic_cast (a) -> initB(); – Christopher

+5

Exceptuando que con la covarianza, el tipo está comprobado estáticamente. – AProgrammer

1

Otro ejemplo es una fábrica de concreto que devolvería punteros a las clases concretas en lugar de la abstracta (la he usado para uso interno en la fábrica cuando la fábrica tuvo que construir objetos compuestos).

+0

Presumiblemente, ¿dinámicamente_cast los punteros para averiguar qué instancia concreta tiene en realidad? En ese caso, no necesita la covarianza. ¿O me estoy perdiendo algo? –

+0

'dynamic_cast' requiere que los tipos sean polimórficos y potencialmente costosos. Sin embargo, el mismo resultado se puede lograr usando una plantilla de función en la clase de fábrica. Esto llevaría a cabo dos tareas: verificar que los tipos estén relacionados (isbaseclass o lo que sea) y luego convertir el resultado genérico al tipo derivado. No se necesita un lanzamiento dinámico. –

+0

Por potencialmente caro, me refiero a más costoso que un static_cast e incluso entonces es solo cuando lo llama millones de veces que hace la diferencia. ;) –

4

Creo que la covarianza puede ser útil al declarar métodos de fábrica que devuelven una clase específica y no su clase base. This article explica este escenario bastante bien, e incluye lo siguiente ejemplo de código:

class product 
{ 
    ... 
}; 

class factory 
{ 
public: 
    virtual product *create() const = 0; 
    ... 
}; 

class concrete_product : public product 
{ 
    ... 
}; 

class concrete_factory : public factory 
{ 
public: 
    virtual concrete_product *create() const 
    { 
     return new concrete_product; 
    } 
    ... 
}; 
1

Se hace útil en el escenario en el que desea uso una fábrica de hormigón para generar productos de hormigón. Siempre quiere la interfaz más especializada que sea lo suficientemente general ...

El código utilizando la fábrica de hormigón puede suponer con seguridad que los productos son de hormigón, por lo que puede utilizar de forma segura las extensiones proporcionadas por la clase de producto de hormigón con respecto a la clase abstracta. Esto podría considerarse azúcar sintáctico, pero de todos modos es dulce.

13

En general, la covarianza le permite expresar más información en la interfaz de clase derivada que la verdadera en la interfaz de clase base. El comportamiento de una clase derivada es más específico que el de una clase base, y la covarianza expresa (un aspecto de) la diferencia.

Es útil cuando tiene jerarquías relacionadas de gubbins, en situaciones donde algunos clientes querrán usar una interfaz de clase base, pero otros clientes usarán la interfaz de clase derivada.Con const-corrección omitido:

class URI { /* stuff */ }; 

class HttpAddress : public URI { 
    bool hasQueryParam(string); 
    string &getQueryParam(string); 
}; 

class Resource { 
    virtual URI &getIdentifier(); 
}; 

class WebPage : public Resource { 
    virtual HttpAddress &getIdentifier(); 
}; 

clientes, que saben que tienen una página web (navegadores, tal vez) sabe que es significativo a la vista Parámetros de consulta. Los clientes que usan la clase base Recurso no conocen tal cosa. Siempre vincularán el HttpAddress& devuelto a una variable URI& o temporal.

Si sospechan, pero no saben, que su objeto Resource tiene una HttpAddress, entonces pueden dynamic_cast. Pero la covarianza es superior a "solo saber" y hacer el reparto por la misma razón por la que el tipado estático es útil en absoluto.

Hay alternativas - pegue la función getQueryParam en URI pero haga que hasQueryParam devuelva falso para todo (satura la interfaz URI). Deje WebPage::getIdentifier definido para devolver URL&, devolviendo realmente un HttpIdentifier&, y las personas que llaman realizan un inútil dynamic_cast (desordena el código de llamada y la documentación de WebPage donde dice "se garantiza que la URL devuelta se puede convertir dinámicamente en HttpAddress"). Agregue una función getHttpIdentifier a WebPage (satura la interfaz WebPage). O simplemente use la covarianza para lo que se supone que debe hacer, que es expresar el hecho de que un WebPage no tiene un FtpAddress o un MailtoAddress, tiene un HttpAddress.

Por último, existe un argumento razonable de que no debería haber jerarquías de gubbins, y menos relacionadas con las jerarquías de gubbins. Pero esas clases podrían fácilmente ser interfaces con métodos virtuales puros, así que no creo que afecte la validez de usar la covarianza.

+2

gubbins? Esa no es una palabra con la que esté familiarizado. Google lo produce desde UrbanDictionary.com: \t un Gubbin es alguien que en ocasiones actúa como un vagabundo "usted no cree que sus padres son dueños de una mansión haría que el Gubbin?!" supongo que hay un significado diferente en una. contexto de programación? –

+0

'fraid not. Simplemente significa "cosas", "cosas". –

1

A menudo me encuentro usando covarianza cuando trabajo con el código existente para deshacerme de static_casts. Por lo general, la situación es similar a esto:

class IPart {}; 

class IThing { 
public: 
    virtual IPart* part() = 0; 
}; 

class AFooPart : public IPart { 
public: 
    void doThis(); 
}; 

class AFooThing : public IThing { 
    virtual AFooPart* part() {...} 
}; 

class ABarPart : public IPart { 
public: 
    void doThat(); 
}; 

class ABarThing : public IThing { 
    virtual ABarPart* part() {...} 
};  

Esto me permite

AFooThing* pFooThing = ...; 
pFooThing->Part()->doThis(); 

y

ABarThing pBarThing = ...; 
pBarThing->Part()->doThat(); 

en lugar de

static_cast<AFooPart>(pFooThing->Part())->doThis(); 

y

static_cast<ABarPart>(pBarThing->Part())->doThat(); 

Ahora, al encontrar este código, se podría discutir sobre el diseño original y si hay uno mejor, pero en mi experiencia a menudo existen limitaciones como las prioridades, el costo/beneficio, etc. que interfieren con el embellecimiento extenso del diseño y permita solo pequeños pasos como este.

Cuestiones relacionadas