2009-07-05 20 views
5

Supponiamo che Y sia una classe derivata dalla classe X e X dichiara foo di essere virtuale. Supponiamo che y sia di tipo (Y *). Quindi ((X *) y) -> foo() eseguirà la versione Y di foo(), ma ((X) * y) .foo() eseguirà la versione X. Puoi dirmi perché il polimorfismo non si applica nel caso dereferenziato? Mi aspetterei che la sintassi produrrebbe la versione Y di foo().polimorfismo C++ ((X *) y) -> foo() vs ((X) * y) .foo()

risposta

0

Credo sia dovuto semplicemente al modo in cui viene specificata la lingua. I riferimenti e i puntatori utilizzano l'associazione tardiva laddove possibile, mentre gli oggetti usano l'associazione anticipata. In ogni caso sarebbe possibile eseguire un binding posticipato (immagino), ma un compilatore che lo facesse non avrebbe seguito le specifiche C++.

8

Un cast sempre (*) crea un nuovo oggetto del tipo a cui stai trasmettendo, che è costruito usando l'oggetto che stai lanciando.

Passare a X * crea un nuovo puntatore (ovvero un oggetto di tipo X *). Ha lo stesso valore di , quindi punta allo stesso oggetto, di tipo Y.

Il casting su X crea una nuova X. È costruito usando *y, ma altrimenti non ha nulla a che fare con il vecchio oggetto . Nell'esempio, foo() viene chiamato su questo nuovo oggetto "temporaneo", non sull'oggetto puntato da .

Si è corretto che il polimorfismo dinamico si applica solo a puntatori e riferimenti, non a oggetti, e questo è il motivo per cui: se si ha un puntatore a X, la cosa a cui punta potrebbe essere una sottoclasse di X. Ma se hai una X, allora è una X e nient'altro. le chiamate virtuali sarebbero inutili.

(*) a meno che l'ottimizzazione non consenta l'omissione di codice che non modifica il risultato. Ma l'ottimizzazione non è autorizzata a cambiare la funzione foo().

+1

... e ((X &) * y) .foo() _does_ chiama il derivato foo, perché il casting di una Y e di una X e non crea una nuova X. – Doug

+0

Sì, probabilmente dovrei dire "crea sempre un nuovo oggetto del tipo a cui stai trasmettendo, a meno che il tipo a cui stai trasmettendo non sia un tipo di oggetto". La trasmissione al tipo di riferimento "crea" un nuovo riferimento associato all'oggetto originale. –

0

Penso spiegazione di Darth Eru è corretta, ed ecco perché penso che C++ si comporta in questo modo:

Il codice (X) * y è come la creazione di una variabile locale che è di tipo X. Il compilatore ha bisogno di allocare lo spazio sizeof (X) nello stack e getta via tutti i dati extra inclusi in un oggetto di tipo Y, quindi quando si chiama foo() deve eseguire la versione X. Sarebbe difficile per il compilatore comportarsi in un modo che consentisse di chiamare la versione Y.

Il codice (X *) y è come creare un puntatore a un oggetto e il compilatore sa che l'oggetto puntato è X o una sottoclasse di X. Al runtime quando si denega il puntatore e si chiama pippo con "- > foo() "la classe dell'oggetto viene determinata e viene utilizzata la funzione corretta.

+0

Ricorda che (X) * y è definito come X (* y), cioè una chiamata a qualche costruttore di X per creare un nuovo oggetto X. Non sarebbe solo difficile, non è nemmeno auspicabile che un oggetto X abbia la versione Y di foo() chiamata su di essa, solo perché è stato costruito usando un oggetto Y. –

2

Il dereferencing (la parte *y) va bene, ma il cast (la parte (X)) crea un nuovo oggetto (temporanea) in particolare di classe X - questo è ciò che significa fusione . Quindi, l'oggetto deve avere la tabella virtuale dalla classe X - considera che la trasmissione avrà rimosso tutti i membri di istanza aggiunti da Y nella sottoclasse (in effetti, come potrebbe il copyctor di X essere a conoscenza di essi?), Quindi Sarebbe potenzialmente un disastro se dovessero essere eseguite le sostituzioni di Y, assicurandosi che this punti a un'istanza di Y, completa di membri aggiunti e tutto ... quando quella conoscenza era falsa!

La versione in cui lanci puntatori è ovviamente completamente diverso - il *X ha appena gli stessi bit come il Y*, quindi è ancora puntando a un'istanza perfettamente valida di Y (anzi, è indicando y, naturalmente).

Il fatto triste è che, per ragioni di sicurezza, il copy ctor di una classe dovrebbe essere chiamato solo con, come argomento, un'istanza di quella classe - non di alcuna sottoclasse; la perdita di membri di istanze aggiunti & c è semplicemente troppo dannosa. Ma l'unico modo per assicurarsi che sia quello di seguire l'eccellente consiglio di Haahr, "Non sottoclassi le classi concrete" ... anche se sta scrivendo su Java, il consiglio è almeno altrettanto valido per C++ (che ha questo copy ctor " affettare "problema in aggiunta! -)

+0

Se la perdita dei membri extra di Y è dannosa durante la costruzione di una X, allora la classe Y non soddisfa il principio di sostituzione di Liskov, o più probabilmente il chiamante non ha realizzato che sta lanciando (fetta non intenzionale) e pensa di avere ancora un oggetto della classe Y. In ogni caso, non penso che l'errore sia, come tale, chiamare il costruttore di copie con una Y. Non è in grado di apprezzare che il risultato sia una X costruita dalla Y come da qualsiasi altro costruttore di 1-arg e non qualcosa di polimorfico. –

+0

La slice non intenzionale è la causa più probabile, e se non si sottoclasse classi concrete (oltre alle altre ragioni fornite da Haahr), questo è un tipo di incidente che non si verificherà! -) –

+0

Certo, è una buona regola di pollice. Sono d'accordo anche con Haahr ci sono casi in cui vale la pena di cambiare il braccio e la sottoclasse, perché a volte l'ereditarietà è effettivamente utile. Ma in realtà deve essere possibile soddisfare Liskov, che è dove la maggior parte delle sottoclassi di classi concrete sbagliano. Se, come dice Haahr, * qualsiasi * aspetto della superclasse è "trascinato inavvertitamente", allora hai già fallito. Ma puoi sottoclassi lezioni concrete, nei rari casi ha senso. Se è così, anche il copy ctor ha senso, e quelli che capiscono cosa è lo slicing lo eviteranno comunque, non facendolo. –

10

Si sta affettando la parte dell'oggetto Y e si copia l'oggetto in un oggetto X. La funzione richiamata viene richiamata su un oggetto X e viene quindi chiamata la funzione X.

Quando si specifica un tipo in C++ in una dichiarazione o un cast, ciò significa che l'oggetto dichiarato o fuso è in realtà di quel tipo, non di un tipo derivato.

Se si vuole trattare solo l'oggetto viene di tipo X (vale a dire, se si desidera che il tipo statico dell'espressione essere X, ma non vuole rinunciare ad indicare un oggetto Y) allora si lanci in una riferimento tipo

((X&)*y).foo() 

Questo chiamerà la funzione nell'oggetto Y, e non tagliare né copiare in un oggetto X. Nei passaggi, questo fa

  • dereference il puntatore y, che è di tipo Y*. La dereferenziazione produce un valore lvalue espressione di tipo Y. Un'espressione lvalue può effettivamente denotare un oggetto di un tipo derivato, anche se il suo tipo statico è quello della sua base.
  • Trasmissione a X&, che è un riferimento a X. Ciò produrrà un valore lvalue espressione di tipo X.
  • Chiamare la funzione.

tuo cast originale hanno fatto

  • risoluzione del riferimento del puntatore y.
  • L'espressione risultante convertita in X. Ciò si tradurrà in un'operazione di copia in un nuovo oggetto X. L'espressione risultante è valore espressione di tipo statico X. Il tipo dinamico dell'oggetto indicato è ancheX, così come tutte le espressioni valore.
  • Chiamare la funzione.
Problemi correlati