2009-08-11 12 views
27

This question è stato chiesto qui poche ore fa e mi ha fatto capire che non ho mai effettivamente utilizzato tipi di ritorno covarianti nel mio codice. Per quelli non è sicuro quale covarianza sia, è possibile che il tipo di ritorno delle (in genere) virtuali funzioni differiscano a condizione che i tipi siano parte della stessa gerarchia ereditarietà . Ad esempio:Quando covarianza C++ è la soluzione migliore?

I diversi tipi di ritorno delle due funzioni f() si dicono covarianti. Le vecchie versioni di C++ necessari i tipi di ritorno per essere lo stesso, in modo da B dovrebbe assomigliare:

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

Quindi, la mia domanda: Qualcuno ha un esempio del mondo reale in cui covariante di ritorno sono necessari tipi di funzioni virtuali , o produrre una soluzione superiore alla semplice restituzione di un puntatore o riferimento di base?

+0

Quando ho letto la domanda la mia reazione era esattamente la stessa. Qual è il problema: non utilizzare i tipi di ritorno covarianti! –

risposta

27

L'esempio canonico è un metodo .clone()/.copy(). Quindi puoi sempre fare

obj = obj->copy(); 

indipendentemente dal tipo di oggetto.

Modifica: questo metodo di clonazione sarebbe definito nella classe di base dell'oggetto (come in realtà è in Java). Quindi, se clone non fosse covariante, dovresti eseguire il cast, o essere limitato ai metodi della classe base root (che avrebbe solo pochissimi metodi, rispetto alla classe dell'oggetto sorgente della copia).

+2

Ma non dovresti (IMHO) preoccuparti di quale tipo sia A * a = some_a_or_b-> clone(); funziona altrettanto bene, e quindi usi altri metodi virtuali sul puntatore clonato. –

+0

Vedo la covarianza più come zucchero sintattico, quindi come una caratteristica necessaria. Puoi sempre fare A * a = b-> clone(); dynamic_cast (a) -> initB(); – Christopher

+5

Tranne che con covarianza il tipo viene controllato staticamente. – AProgrammer

1

Un altro esempio è una fabbrica di calcestruzzo che restituisce puntatori alle classi concrete anziché a quella astratta (l'ho usata per uso interno in fabbrica quando la fabbrica doveva costruire oggetti composti).

+0

Presumibilmente hai dynamic_cast i puntatori per capire quale istanza concreta hai effettivamente ottenuto? Nel qual caso non hai bisogno della covarianza. O mi sta sfuggendo qualcosa? –

+0

'dynamic_cast' richiede che i tipi siano polimorfici ed è potenzialmente costoso. Tuttavia, lo stesso risultato può essere ottenuto utilizzando un modello di funzione nella classe factory. Questo eseguiva due operazioni: verifica che i tipi siano correlati (isbaseclass o qualsiasi altra cosa) e quindi restituisca il risultato generico al tipo derivato. Non è necessario alcun cast dinamico. –

+0

Con un costo potenzialmente elevato intendo più costoso di un static_cast e anche in questo caso è solo dove lo chiami milioni di volte che fa la differenza. ;) –

4

Penso che la covarianza possa essere utile quando si dichiarano metodi factory che restituiscono una classe specifica e non la sua classe base. This article spiega questo scenario abbastanza bene, e include il seguente esempio di codice:

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

diventa utile nello scenario in cui si desidera uso una fabbrica di cemento per generare prodotti in calcestruzzo. Si vuole sempre l'interfaccia più specializzata che è abbastanza generale ...

Il codice utilizzando la fabbrica di cemento può tranquillamente supporre che i prodotti sono prodotti in calcestruzzo, in modo che possa tranquillamente utilizzare le estensioni fornite dalla classe del prodotto cemento con riguardo alla classe astratta. Questo potrebbe essere considerato come zucchero sintattico, ma è comunque dolce.

13

In generale, la covarianza consente di esprimere più informazioni nell'interfaccia di classe derivata rispetto a quella reale nell'interfaccia di classe base. Il comportamento di una classe derivata è più specifico di quello di una classe base e la covarianza esprime (un aspetto della) la differenza.

È utile quando si hanno gerarchie correlate di gubbins, in situazioni in cui alcuni client desiderano utilizzare un'interfaccia di classe base, ma altri client utilizzeranno l'interfaccia di classe derivata.Con const correttezza omesso:

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(); 
}; 

I clienti che sanno di avere una pagina web (browser, forse) sapere che è significativo per guardare ai parametri di query. I client che utilizzano la classe di base delle risorse non conoscono questa cosa. Legheranno sempre il valore restituito HttpAddress& a una variabile URI& o temporanea.

Se sospettano, ma non sanno, che il loro oggetto Risorsa ha un indirizzo Http, possono dynamic_cast. Ma la covarianza è superiore a "conoscere solo" e fare il cast per la stessa ragione per cui la tipizzazione statica è utile a tutti.

Esistono alternative: attivare la funzione getQueryParam su URI ma rendere hasQueryParam restituire false per tutto (ingombrare l'interfaccia URI). Lasciare WebPage::getIdentifier definito per restituire URL&, restituendo effettivamente un valore HttpIdentifier& e fare in modo che i chiamanti facciano un inutile dynamic_cast (ingombrare il codice chiamante e la documentazione di WebPage in cui si dice "l'URL restituito è garantito per essere digitalizzabile in HttpAddress"). Aggiungere una funzione getHttpIdentifier a WebPage (ingombra l'interfaccia WebPage). O semplicemente usa la covarianza per quello che è destinato a fare, che è esprimere il fatto che uno WebPage non ha uno FtpAddress o uno MailtoAddress, ha uno HttpAddress.

Infine c'è ovviamente un ragionevole argomento che non si dovrebbero avere gerarchie di gubbins, per non parlare delle gerarchie correlate di gubbins. Ma quelle classi potrebbero facilmente essere interfacce con metodi virtuali puri, quindi non penso che influenzi la validità dell'uso della covarianza.

+2

gubbins? Non è una parola con cui ho familiarità. UrbanDictionary.com: \t un gubbin è qualcuno che a volte si comporta come un vagabondo "Tu non credere ai suoi genitori possiedono un palazzo faresti Il gubbin?" presumo ci sia un significato diverso in una. contesto di programmazione? –

+0

'non lo è significa "roba", "cose". –

1

Spesso mi trovo a utilizzare la covarianza quando si lavora con il codice esistente per sbarazzarsi di static_casts. Di solito la situazione è simile a questo:

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() {...} 
};  

Questo mi permette di

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

e

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

invece di

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

e

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

Ora, quando imbattersi in tale codice si potrebbe sostenere circa il disegno originale e se c'è uno migliore - ma nella mia esperienza ci sono spesso dei vincoli, come priorità, costi/benefici, ecc, che interferiscono con una vasta abbellimento disegno e permetti solo piccoli passi come questo.

Problemi correlati