C ++ non può convertire da base A di tipo derivato B tramite base A virtuale
-
04-10-2019 - |
Domanda
Ho tre classi:
class A {};
class B : virtual public A {};
class C : virtual public A {};
class D: public B, public C {};
Il tentativo di un cast statica da A a B * * ottengo l'errore di seguito:
cannot convert from base A to derived type B via virtual base A
Soluzione
Al fine di comprendere il sistema delle caste è necessario tuffo nel modello a oggetti.
La rappresentazione classica di un semplice modello di gerarchia è di contenimento:. Che se deriva da B
A
allora l'oggetto B
dovrà infatti contenere un oggetto secondario A
a fianco i propri attributi
Con questo modello, downcasting è una semplice manipolazione dei puntatori, da un offset noto al momento della compilazione che dipende dal layout di memoria B
.
Questo è quello static_cast do:. Un getto statico è doppiato statico perché il calcolo di quanto necessario per il getto viene fatto al momento della compilazione, sia esso puntatori o conversioni (*)
Tuttavia, quando calci eredità virtual
nelle cose tendono a diventare un po 'più difficile. Il problema principale è che con virtual
eredità tutte le sottoclassi condividono una stessa istanza del sotto-oggetto. Per fare questo, B
avrà un puntatore a una A
, invece di un A
corretta, e l'oggetto classe base A
sarà istanziata fuori di B
.
Pertanto, è impossibile al momento della compilazione per essere in grado di dedurre la necessaria aritmetica dei puntatori. Dipende dal tipo runtime dell'oggetto
Ogni volta che c'è un tipo di runtime dipendenza, è necessario RTTI (Runtime Informazioni Tipo), e facendo uso di RTTI per calchi è il lavoro di dynamic_cast .
In sintesi:
- in fase di compilazione bassi:
static_cast
- run-time bassi:
dynamic_cast
Gli altri due sono anche calchi in fase di compilazione, ma sono così specifici che è facile da ricordare quello che sono per ... e sono maleodoranti, quindi non meglio usare a tutti comunque.
(*) Come notato da @curiousguy nei commenti, questo vale solo per downcasting. Un static_cast
permette upcasting indipendentemente eredità virtuale o semplice, anche se poi il cast è anche inutile.
Altri suggerimenti
Per quanto ne so, è necessario utilizzare dynamic_cast
perché l'eredità è virtual
e sei downcasting.
Non è possibile utilizzare static_cast
in questa situazione perché il compilatore non conosce l'offset di B rispetto ad A in fase di compilazione. L'offset deve essere calcolato in runtime in base al tipo esatto dell'oggetto più derivata. Pertanto è necessario utilizzare dynamic_cast
.
Si, è necessario utilizzare un dynamic_cast, ma dovrete fare la A polimorfica classe di base, ad esempio, con l'aggiunta di un dtor virtuale.
Secondo documenti standard,
Sezione 5.2.9 - 9 , Static Fusioni ,
Un rvalue di tipo “puntatore a CV1 B”, dove B è un tipo di classe, può essere convertito in un rvalue di tipo “puntatore a cv2 D “, dove D è una classe derivata (punto 10) da B, se una conversione standard valida dal‘puntatore a D’a‘puntatore B’ esiste (4.10), CV2 è la stessa cv-qualifica, o maggiore cv-qualificazione che, CV1 e B è né una classe base virtuale D né una classe di base di una classe base virtuale di D.
Quindi, non è possibile e si dovrebbe usare dynamic_cast
...
$ 5.2.9 / 2 "Un'espressione e può essere esplicitamente convertito in un tipo T usando uno static_cast della forma static_cast (e) se la dichiarazione “T t (e);” è ben formata, per alcuni inventato t temporanea variabile (8,5) ".
Nel codice che si sta tentando static_cast con 'T = B *' e 'E = A *'
Ora 'B * t (A *)' non è ben formata in C ++ (ma 'A * t (B *)' perché 'A' è una base virtuale univoco e accessibile di 'B'. Pertanto la il codice dà l'errore.
Non so se questo è "sicuro", ma.
Supponendo
B derivata da A (e A puro virtuale)
Poiché so che un puntatore a B rimane ancora un puntatore a B.
class A
{
virtual void doSomething(const void* p) const =0;
};
class B
{
public:
int value;
virtual void doSomething(const void*p)const
{
const B * other = reinterpret_cast<const B*>(p);
cout<<"hello!"<< other->value <<endl;
}
};
int main()
{
B foo(1),bar(2);
A * p = &foo, q=&bar;
p->doSomething(q);
return 0;
}
Questo programma esegue correttamente e restituiscono la stampa "ciao!" e il valore dell'altro oggetto (in questo caso "2").
A proposito, quello che sto facendo è altamente pericoloso (personalmente dare un ID diverso per ogni classe e affermo dopo cr fusione che la corrente ID è uguale a altro ID per essere sicuri che stiamo facendo qualcosa con 2 classi uguali ) e come vedete mi sono limitato a metodi "const". Così questo funzionerà con metodi "non-const", ma se fai qualcosa di sbagliato cattura il bug sarà quasi Unpossible. E anche con l'affermazione c'è un out 1 possibilità su 4 miliardi di riuscire affermazione anche quando si suppone a fallire (Assert (ID == altri-> ID);)
A proposito .. Un buon design OO non dovrebbe richiedere questo genere di cose, ma nel mio caso ho cercato di refactoring / ri-progettare il codice senza essere in grado di abbandonare l'uso di cr casting. in generale si può evitare questo tipo di cose.