9
votes

Héritage multiple: résultat inattendu après la jonction de Void * à la deuxième classe de base

Mon programme doit utiliser le vide * Afin de transporter des données ou des objets dans une situation d'invocation dynamique, de manière à pouvoir faire référence aux données des types arbitraires, même des types primitifs. Toutefois, j'ai récemment découvert que le processus de casting à basse-coiffe * en cas de classes avec plusieurs classes de base échoue et bloque même mon programme après avoir invoqué des méthodes sur ces pointeurs couplés, même si les adresses de mémoire semblent être correctes. L'accident arrive lors de l'accès à "Tablevis".

J'ai donc créé un petit cas de test, l'environnement est GCC 4.2 sur Mac OS X: P> xxx pré>

produit la sortie suivante : P>

Decorated (direct)30,30
Shape 30,30
Square 30,30
Decorated (per void*) 73952,73952
DecoratedSquare 30,30


2 commentaires

Vous ne pouvez utiliser que REINTERPRET_CAST à lancer à Void *, puis revenez au type d'origine. Vous pouvez PAS CASSER à NOID * et puis à autre chose.


Vous seriez beaucoup mieux à utiliser le motif de décorateur que MI, pas que cela résoudrait la coulée du problème vidimal *.


3 Réponses :


2
votes

Un statique_cast ou une dynamique_cast en présence de multiples héritage peut modifier la représentation du pointeur en le compensant de manière à désigner la bonne adresse. static_cast détermine le décalage correct en considérant des informations de saisie statiques. dynamic_cast le fait en cochant le type dynamique. Si vous allez jeter le vide *, vous perdez toutes les informations de saisie statiques et la possibilité d'obtenir des informations de saisie dynamiques, de sorte que le réinterpret_cast que vous utilisez est en supposant que le décalage est NULL, échouant parfois.


5 commentaires

Cela signifie-t-il donc effectivement que le vide * ne sont pas réalisables dans de multiples situations de héritage?


Il est réalisable, tant que vous êtes toujours jeté sur le type réel de l'objet, pas une classe de base.


Voir mon commentaire sur la réponse de Mike


Plus précisément, vous devez lancer le vide * au type statique La valeur avait avant d'être lancée en annulation *.


Non seulement cela, un static_cast ou une conversion implicite peut également utiliser des informations de type dynamique, en lisant le VPTR ou une autre alternative de mise en œuvre.



9
votes

Ce n'est pas un bug de compilateur - c'est ce que reterpret_cast fait. L'objet décoratedsquare sera aménagé dans la mémoire quelque chose comme ceci: xxx

convertir un pointeur sur celui-ci sur Void * donnera à l'adresse du début de ces données, sans savoir quel type existe-t-il. Reterpret_cast prendra cette adresse et interprétera tout ce qui existe en tant que décoré - mais le contenu de la mémoire réelle est le carré . Ceci est faux, vous obtenez donc un comportement indéfini.

Vous devez obtenir les résultats corrects si vous Reterpret_cast au type dynamique correct (c'est décoratedsquare ) , puis convertir en classe de base.


10 commentaires

Qui l'explique, mais cela signifierait que sans connaissance de la sous-classe concrète, je ne peux pas produire de code qui fonctionne sur des pointeurs sur l'une des classes de base de manière polymorphe, comme il serait possible dans des situations d'héritage unique, où l'appelant n'a pas besoin connaître la sous-classe concrète. En d'autres termes, il ne serait pas possible de compiler un cadre et de l'utiliser plus tard lorsque les sous-classes concrètes sont connues? Cela signifie que de multiples héritage en C ++ n'est pas complet.


MI en C ++ est terminé, sauf si vous jetez les informations de type. Vous pouvez utiliser virtual fonctionner pour obtenir un comportement polymorphique sans connaître des sous-classes en béton si vous ne sortez pas sauvagement sur des types erronés.


Pour moi, comme je ne connais tout simplement pas le sous-type concret dans cette partie du cadre, cela signifie simplement que je ne peux pas utiliser de vide * là-bas. Je dois transporter des informations de type en quelque sorte en utilisant une "n'importe quel" ou similaire. Pas bien et pas ce que j'avais attendu :( Mais au moins tu m'as donné une bonne explication. Merci tout le monde


@Andre, ce n'est pas un problème de héritage multiple, c'est un problème de lancer des choses à annuler *. Ce qui fonctionne, je jette un pointeur à vide * puis de retour à son type d'origine statique (non dynamique comme Mike semble impliquer). Jeter un vide * à quelque chose d'autre est indéfini (Eh bien, il y a quelques exceptions près)


Vous pourrez peut-être faire ce que vous voulez avec un REINERPRET_CAST à SHACE suivi d'un dynamic_cast à décoré . Je ne suis pas sûr à 100%, donc je ne l'ajouterai pas à la réponse. Il peut bien donner un comportement non défini.


Les modes naturels de construire un cadre utilisent des modèles ou des types abstraits. Void * est une façon de faire des trucs de niveau bas et ne doit pas être visible dans votre cadre.


@MIKE, Ici, le type statique original est décoréquare, de sorte que le vide * doit être coulé à la décoration desquels. Si le type statique original était de la forme *, le vide * doit être coulé à la forme * (et éventuellement, puis a été déposé de manière dynamique à décorarésquare).


Mike tu es mon héros! Pas ça marche: dynamic_cast (réinterpret_cast (VP)); Est-ce que cela me donne et me donne: décoré (par vide *) 10,10 Je dois voir comment cela peut être utilisé dans le cadre, mais je pense que cela devrait être possible, car je connais toujours la première classe de base là-bas. Merci


@Andre, êtes-vous sûr de ne pas pouvoir transporter des trucs autour de la forme * au lieu de vide *? Comme vous l'avez maintenant, bien que comment connaissez-vous un pointeur est une forme * et non INT * (ou autre type primitif)?


@quamrana je pouvais mais avec beaucoup plus d'effort, surtout que j'aurais besoin d'envelopper toutes les primitives avec des objets et de tout donner une classe de base commune. Cette partie-cadre est générée du code qui prend quelques vides * et les traduit en paramètres à un appel de méthode normal. De cette façon, je reçois une invocation de méthode dynamique, une caractéristique essentielle de mon cadre. Il ressemble beaucoup au concept de signal / machines à sous QT, ils utilisent les mêmes méthodes (nulk *) là-bas.



14
votes

Répéter dix fois - la seule chose que vous pouvez faire en toute sécurité avec un REINERPRET_CAST Le pointeur est Reterpret_cast Retour au même type de pointeur. Il en va de même avec les conversions sur VOID * : Vous devez reconvertir le type d'origine.

Donc, si vous lancez un décoratedsquare * à Void * < / Code>, vous devez le jeter à décoratedSquare * . Pas décoré * , pas carré * , pas forme * . Certains d'entre eux pourraient travailler sur votre machine, mais il s'agit d'une combinaison de bonne chance et de comportement spécifique à la mise en œuvre. Cela fonctionne généralement avec une seule héritage, car il n'y a pas de raison évidente de mettre en œuvre des pointeurs d'objet d'une manière qui l'empêcherait de fonctionner, mais cela n'est pas garanti, et cela ne peut pas fonctionner en général pour une héritage multiple.

Vous dites que votre code accède à "types arbitraires, y compris types primitifs" via un vide *. Il n'y a rien de mal à cela - vraisemblablement qui reçoit les données sait à le traiter comme un décoratedsquare * et non comme, dire, int * .

Si quiconque reçoit seulement qu'il ne sait que pour le traiter comme une classe de base, telle que décorée * , puis celui qui le convertit sur vide * doit static_cast La classe de base d'abord, puis à Void * : xxx

maintenant lorsque vous lancez décorated_vp retour à décorée * , vous obtiendrez le résultat de static_cast (ds) , ce dont vous avez besoin.


2 commentaires

Steve merci pour cela. Je suppose beaucoup ça. En effet, il est toujours assuré que, dans la partie appelante du framework, il existe un type de pointeur de base connu qui est lancé à vide * puis transporté à l'extrémité de réception, qui connaît 2 choses: la classe de base la plus haute et une sous-classe où une méthode est défini. Il ne sait cependant pas la sous-classe concrète. Mais cela semble être correct maintenant, car l'extrémité réceptrice toujours réinterpret_casts à la même classe de base la plus haute que l'extrémité d'envoi a fait, puis une dynamique_cast ou statique_cast à la sous-classe intermédiaire où elle invoque la méthode souhaitée.


+1 pour "La seule chose que vous pouvez faire en toute sécurité avec un pointeur de réinterpret_cast est réinterpret_cast le revenant au même type de pointeur." Résume très bien la situation.