Bluehorn réponse est bonne, mais pour moi ça n'explique pas la raison du problème en termes plus simples. La façon dont je le comprends, il est comme suit:
Si NonPOD est un non-POD classe, puis quand vous le faites:
NonPOD np;
np.field;
le compilateur n'a pas nécessairement accès du terrain en y ajoutant un peu décalée par rapport à la base de pointeur et de déférence. Pour un module de classe, la Norme C++ contraint à le faire(ou quelque chose d'équivalent), mais pour un non-POD classe, il ne le fait pas. Le compilateur peut, au lieu de lire un pointeur en dehors de l'objet, d'ajouter un décalage à qui la valeur à donner à l'emplacement de stockage du champ, puis de déréférencement. C'est un mécanisme commun avec l'héritage virtuel si le champ est un membre d'une base virtuelle de NonPOD. Mais elle n'est pas limitée à ce cas. Le compilateur peut faire à peu près tout ce qu'il aime. Il pourrait un appel masqué généré par le compilateur fonction membre virtuelle si elle veut.
Dans les cas complexes, il n'est évidemment pas possible de représenter l'emplacement du champ comme un nombre entier de décalage. Donc, offsetof
n'est pas valide sur la non-POD classes.
Dans le cas où votre compilateur se trouve juste à stocker l'objet dans une façon simple (tels que l'héritage simple, et normalement, même les non-virtuel de l'héritage multiple, et, normalement, les champs définis dans la classe que vous faites référence à l'objet plutôt que par rapport à certains de la classe de base), alors il sera juste pour arriver à travailler. Il y a probablement des cas qui vient d'arriver à travailler sur chaque compilateur, il est. Ce n'est pas valide.
Annexe: comment l'héritage virtuel de travail?
Avec l'héritage simple, si B est Un dérivé de la mise en oeuvre habituelle est qu'un pointeur vers B est simplement un pointeur vers Un, avec B données supplémentaires coincé sur la fin:
A* ---> field of A <--- B*
field of A
field of B
Avec un simple héritage multiple, en général, vous supposer que B est une classe de base (appeler 'em A1 et A2) sont disposées dans un ordre particulier à B. Mais le même truc avec les pointeurs ne peuvent pas travailler:
A1* ---> field of A1
field of A1
A2* ---> field of A2
field of A2
A1 et A2 "sais" rien sur le fait qu'ils sont à la fois des classes de base de B. Donc, si vous lancez un B* * * * A1*, il est à point pour les domaines de l'A1, et si vous le lancez à A2* il est à point pour les domaines de l'A2. Le pointeur à l'opérateur de conversion s'applique un décalage. Donc, vous pourriez vous retrouver avec ceci:
A1* ---> field of A1 <---- B*
field of A1
A2* ---> field of A2
field of A2
field of B
field of B
Puis, jetant un B* * * * A1* ne change pas la valeur du pointeur, mais un moulage A2* ajoute sizeof(A1)
octets. C'est "l'autre" raison pour laquelle, en l'absence d'un destructeur virtuel, la suppression de B par l'intermédiaire d'un pointeur vers A2 va mal. Il n'est pas simplement de ne pas appeler le destructeur de B et A1, il n'a même pas libre à la bonne adresse.
De toute façon, B "sait" où l'ensemble de ses classes de base sont, ils sont toujours stockés dans le même décalages. Donc, dans cet arrangement offsetof fonctionne encore. La norme n'exige pas des implémentations de faire de l'héritage multiple de cette façon, mais ils le font souvent (ou quelque chose comme ça). Donc offsetof peut fonctionner dans ce cas sur votre mise en œuvre, mais il n'est pas garanti.
Maintenant, ce sujet de l'héritage virtuel? Supposons que B1 et B2 ont tous les deux Un virtuel de base. Cela les rend unique héritage de classes, de sorte que vous pourriez penser que la première astuce ne fonctionne de nouveau:
A* ---> field of A <--- B1* A* ---> field of A <--- B2*
field of A field of A
field of B1 field of B2
Mais s'accrocher. Ce qui se passe quand C dérive (non-pratiquement, pour des raisons de simplicité) de B1 et B2? C ne doit contenir que 1 exemplaire les domaines de l'A. Ces champs peut pas précéder immédiatement le champs de B1, et aussi précéder immédiatement le champs de B2. Nous sommes en difficulté.
Donc, ce que les implémentations pourrait faire à la place est de:
// an instance of B1 looks like this, and B2 similar
A* ---> field of A
field of A
B1* ---> pointer to A
field of B1
Bien que je l'ai indiqué B1* le pointage de la première partie de l'objet après Un sous-objet, je pense (sans prendre la peine de vérifier), de l'adresse ne sera pas là, ça va être le début de A. C'est juste que contrairement à l'héritage simple, les décalages entre l'adresse du pointeur, et l'adresse que j'ai indiqué dans le diagramme, sera de ne jamais être utilisé à moins que le compilateur est certain du type dynamique de l'objet. Au lieu de cela, il sera toujours passer par la méta-information pour atteindre Un correctement. Donc, mes schémas point là, depuis ce décalage sera toujours appliquée pour les utilisations qui nous intéresse.
Le "pointeur" vers Un pourrait être un pointeur ou un décalage, il n'a pas vraiment d'importance. Dans une instance de B1, créé comme un B1, c'points de (char*)this - sizeof(A)
, et même dans une instance de B2. Mais si nous créons un C, il peut ressembler à ceci:
A* ---> field of A
field of A
B1* ---> pointer to A // points to (char*)(this) - sizeof(A) as before
field of B1
B2* ---> pointer to A // points to (char*)(this) - sizeof(A) - sizeof(B1)
field of B2
C* ----> pointer to A // points to (char*)(this) - sizeof(A) - sizeof(B1) - sizeof(B2)
field of C
field of C
Donc, pour accéder à un champ d'Une aide d'un pointeur ou d'une référence à B2 exige plus que juste l'application d'un décalage. Il faut lire le "pointeur vers Un" champ de B2, suivez-le, et alors seulement d'appliquer un décalage, car en fonction de la classe B2 est une base de, ce pointeur va avoir des valeurs différentes. Il n'y a pas une telle chose comme offsetof(B2,field of A)
: il ne peut pas l'être. offsetof sera jamais travailler avec l'héritage virtuel, sur toute mise en œuvre.