J'ai trois classes:
class A {};
class B : virtual public A {};
class C : virtual public A {};
class D: public B, public C {};
En tentant une conversion statique de A * en B *, j'obtiens l'erreur ci-dessous:
cannot convert from base A to derived type B via virtual base A
Afin de comprendre le système de distribution, vous devez plonger dans le modèle d'objet.
La représentation classique d'un modèle de hiérarchie simple est le confinement: que si B
dérive de A
alors l'objet B
contiendra en fait un sous-objet A
à côté de son propres attributs.
Avec ce modèle, le downcasting est une simple manipulation de pointeur, par un décalage connu au moment de la compilation qui dépend de la disposition mémoire de B
.
C'est ce que static_cast faire: une conversion statique est surnommée statique car le calcul de ce qui est nécessaire pour la conversion est effectué au moment de la compilation, que ce soit l'arithmétique du pointeur ou les conversions (*).
Cependant, lorsque virtual
les coups de pied d'héritage dans les choses ont tendance à devenir un peu plus difficiles. Le problème principal est qu'avec l'héritage virtual
, toutes les sous-classes partagent une même instance du sous-objet. Pour ce faire, B
aura un pointeur vers un A
, au lieu d'un A
propre, et l'objet de classe de base A
sera instancié en dehors de B
.
Par conséquent, il est impossible au moment de la compilation de pouvoir déduire l'arithmétique de pointeur nécessaire: cela dépend du type d'exécution de l'objet.
Chaque fois qu'il existe une dépendance de type d'exécution, vous avez besoin de RTTI (RunTime Type Information), et l'utilisation de RTTI pour les conversions est le travail de dynamic_cast.
En résumé:
static_cast
dynamic_cast
Les deux autres sont également des conversions au moment de la compilation, mais ils sont si spécifiques qu'il est facile de se rappeler à quoi ils servent ... et ils sentent mauvais, alors mieux vaut ne pas les utiliser du tout.
(*) Comme indiqué par @curiousguy dans les commentaires, cela ne vaut que pour le downcasting. UNE static_cast
permet la conversion ascendante indépendamment de l'héritage virtuel ou simple, bien que la conversion soit également inutile.
Pour autant que je sache, vous devez utiliser dynamic_cast
parce que l'héritage est virtual
et que vous effectuez un downcasting.
Vous ne pouvez pas utiliser static_cast
dans cette situation car le compilateur ne connaît pas le décalage de B par rapport à A au moment de la compilation. Le décalage doit être calculé au moment de l'exécution en fonction du type exact de l'objet le plus dérivé. Vous devez donc utiliser dynamic_cast
.
Oui, vous devez utiliser un dynamic_cast, mais vous devrez rendre la classe de base A polymorphe, par exemple en ajoutant un dtor virtuel.
Selon les documents standard,
Section 5.2.9 - 9 , pour Cast statique,
Une valeur r de type "pointeur vers cv1 B", où B est un type de classe, peut être convertie en une valeur r de type "pointeur vers cv2 D", où D est une classe dérivée (article 10) de B, si une norme valide la conversion de "pointeur en D" en "pointeur en B" existe (4.10), cv2 est la même qualification cv que, ou plus grande qualification cv que, cv1, et B n'est ni une classe de base virtuelle de D ni une classe de base d'une classe de base virtuelle de D.
Par conséquent, ce n'est pas possible et vous devez utiliser dynamic_cast
...
$ 5.2.9/2- "Une expression e peut être explicitement convertie en un type T en utilisant un static_cast de la forme static_cast (e) si la déclaration" T t (e); "est bien formée, pour certaines variables temporaires inventées t (8.5). "
Dans votre code, vous essayez de static_cast avec 'T = B *' et 'e = A *'
Maintenant, 'B * t (A *)' n'est pas bien formé en C++ (mais 'A * t (B *)' est parce que 'A' est une base virtuelle non ambiguë et accessible de 'B'. Par conséquent, le code donne une erreur .
Je ne sais pas si c'est "sûr" mais.
En supposant
B dérivé de A (et A pur virtuel)
Puisque je SAIS qu'un pointeur vers B reste un pointeur vers 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;
}
ce programme s'exécute et renvoie correctement l'impression "bonjour!" et la valeur de l'autre objet (dans ce cas "2").
par ailleurs, ce que je fais est très dangereux (personnellement, je donne un ID différent à chaque classe et j'affirme après avoir réinterprété le casting que l'ID actuel est égal à l'autre ID pour être sûr que nous faisons quelque chose avec 2 classes égales) et comme vous voyez, je me suis limité aux méthodes "const". Ainsi, cela fonctionnera avec des méthodes "non const", mais si vous faites quelque chose de mal, la capture du bogue sera presque impossible. Et même avec une assertion, il y a 1 chance sur 4 milliards de réussir une assertion même si elle est supposée échouer (assert (ID == other-> ID);)
Soit dit en passant .. Une bonne conception OO ne devrait pas nécessiter ce genre de choses, mais dans mon cas, j'ai essayé de refactoriser/reconcevoir le code sans pouvoir abandonner l'utilisation de la réinterprétation du casting D'une manière générale, vous POUVEZ éviter ce genre de choses.