7
votes

Pods et héritage en C ++ 11. L'adresse de l'adresse de structure == du premier membre?

(J'ai édité cette question pour éviter les distractions. Il y a une question principale qui devrait être effacée avant toute autre question aurait un sens. Toutes mes excuses à quiconque dont la réponse semble moins pertinente.)

Ensemble EXEMPLE SPÉCIFIQUE: P>

struct Derived { // no inheritance
    Base b; // it just contains it instead
};


4 commentaires

C ++ 11 est principalement éloigné de la terminologie "POD" en faveur de Standard-Disparypse et de manière triviale-copible (au moins toutes les exigences intéressantes utilisent ces termes plus larges). Fixez votre question à utiliser la bonne terminologie.


@Benvoigt, bonne idée. Je vais essayer de clarifier cela quand je suis libre ce soir.


C ++ 11 a toujours des pods cependant et sa question a toujours du sens avec la terminologie utilisée.


En outre, tout de "si cela est vrai" devrait être une nouvelle question, elle n'est complètement pas liée au titre.


4 Réponses :


14
votes

Vous ne vous souciez pas de la pod-ness, vous vous souciez de la mise en page standard em>. Voici la définition de la section 9 [Classe] Code>:

Une classe de mise en page standard est une classe qui: p>

  • n'a pas de données non statiques de membres de type de catégorie non standard (ou d'une matrice de tels types) ou de référence, li>
  • n'a aucune fonction virtuelle (10.3) et aucune classe de base virtuelle (10.1), li>
  • a le même contrôle d'accès (clause 11) pour tous les membres de données non statiques, LI>
  • n'a pas de classes de base non normalisées, li>
  • n'a pas de membres de données non statiques dans la classe la plus dérivée et à la plupart une classe de base avec des membres de données non statiques, ou n'a pas de classes de base avec des membres de données non statiques et li>
  • n'a pas de classes de base du même type que le premier élément de données non statique. LI> ul> blockQuote>

    et la propriété que vous souhaitez est alors garantie (section 9.2 [class.mem] code>): p>

    Un pointeur à un objet de structure de mise en page standard, converti de manière appropriée à l'aide d'un réinterpret_cast code>, pointe vers son élément initial (ou si ce membre est un champ de bits, puis à l'unité dans laquelle il réside ) et vice versa. p> blockQuote>

    Ceci est en fait meilleur que l'ancienne exigence, car la possibilité de réinterpret_cast code> n'est pas perdue en ajoutant des constructeurs non triviaux et / ou destructeurs. P>


    Maintenant, passons à votre deuxième question. La réponse n'est pas ce que vous espériez. P>

    Base *b = new (malloc(sizeof(Derived))) Derived;
    free(b);
    


6 commentaires

Je suis bien conscient de votre déclaration de clôture. En fait, j'ai mis la même phrase au début de ma question :-) Comme je l'ai dit, il y a un précédent sur Stackoverflow pour la communauté réalisant que «non définie» peut parfois être très bien définie; Vous pourriez trouver cette réponse que je suis liée à des questions intéressantes. Je voudrais légèrement plus que "x non défini" d'être convaincu que "y, comme un cas particulier de x, est indéfini". Merci encore pour confirmer certaines étapes de ma logique.


"N'a pas eu de membres de données non statiques dans la classe la plus dérivée et à la plupart une classe de base avec des membres de données non statiques, ni de classes de base avec des membres de données non statiques" ... de sorte que cela signifie dès que vous avez L'héritage, tout sauf des classes de l'arbre d'héritage, il est autorisé à faire partie des membres?


@Martin: Une seule classe dans l'arborescence d'héritage peut avoir des membres de données non statiques. (par induction, car les classes de base doivent également être la disposition standard)


@AARONMCDAID: Si cela fonctionne, cela est dû aux garanties spécifiques à la mise en œuvre de votre fournisseur de compilateur. Selon la norme, c'est un comportement indéfini. Ainsi, un fournisseur de compilateur pourrait légitimement produire un appel à std :: terminer pour l'extrait que vous avez montré. Sauf si leur documentation ne promet autrement.


@Martin, oui qui a l'air assez mauvais. Où as-tu eu cette phrase? (J'ai lu un certain nombre de citations à différents endroits et je ne me souviens plus de rien de plus en plus: - /)


Doh! Je suis aveugle, c'est dans la réponse de @ Benvoigt!



0
votes

Il y a beaucoup de discussions sur des problèmes non pertinents ci-dessus. Oui, principalement pour C Compatibilité, il y a un certain nombre de garanties que vous pouvez compter tant que vous savez ce que vous faites. Tout cela n'est toutefois pas pertinent pour votre question principale. La question principale est la suivante: y a-t-il une situation dans laquelle un objet peut être supprimé à l'aide d'un type de pointeur qui ne correspond pas au type dynamique de l'objet et où le type de type pointu n'a pas de destructeur virtuel. La réponse est: non, il n'y a pas.

La logique pour cela peut être dérivée de ce que le système d'exécution est censé faire: il reçoit un pointeur à un objet et est invité à le supprimer. Il aurait besoin de stocker des informations sur la manière d'appeler destructeurs de classe dérivés ou sur la quantité de mémoire que l'objet prend réellement si cela devait être défini. Cependant, cela impliquerait un coût éventuellement assez substantiel en matière de mémoire utilisée. Par exemple, si le premier membre nécessite un alignement très strict, par exemple Pour être aligné sur une limite de 8 octets, comme c'est le cas pour double , l'ajout d'une taille ajouterait une surcharge d'au moins 8 octets pour allouer la mémoire. Même si cela pourrait ne pas être douteux, cela peut signifier qu'un seul objet au lieu de deux ou quatre s'adapte à une ligne de cache, réduisant sensiblement les performances.


5 commentaires

Ouais Ben, je pense que c'est bien le premier point de ma logique où il y a un doute important dans mon esprit. Cependant, ma supposition était une bonne chance que quelqu'un puisse répondre à cette question "des classes de base, en héritage unique, doit être l'entité initiale d'un objet dérivé, et il ne doit y avoir aucun prépading." Je pourrais éditer la question demain. Mais je devrai complètement digérer ce que j'ai déjà appris de toutes les réponses.


Vous vous demandez où les informations de taille sont stockées. Mais rappelez-vous que tout cela est très bien défini quand il y a un destructeur virtuel. Je suppose que de nombreuses implémentations organisent pour le destructeur virtuel, et / ou des vtables ou quelque chose, de stocker / renvoyer la valeur réelle de «ceci». Par conséquent, je ne crois pas que la taille est stockée de l'endroit où (pas plus que C gratuit doit être informé de la taille). Il suffira dans la plupart des implémentations pour que le système de mémoire sous-jacent soit donné à l'adresse correcte (c'est-à-dire l'adresse exacte nouvelle éd), puis comptez sur son propre ménage interne.


@Aaronmcdaid Je me rends compte que les installations de répartition de la mémoire sous-jacente pourraient stocker la taille quelque part. Cependant, étant donné que la taille est implicite de la taille du type de l'objet ou dérivable du type de béton s'il existe un destructeur virtuel, même l'allocation de mémoire sous-jacente n'a pas besoin de suivre la taille de la taille! Malheureusement, cela se décompose au point où la taille n'est pas transmise à Opérateur Supprimer () Bien qu'il soit connu par le Supprimer Opérateur. Bien entendu, la mise en œuvre pourrait toujours en tirer parti si elle sait que ses propres opérateurs sont utilisés.


Je dois être en désaccord avec votre explication. Bien que la taille de type n'est effectivement pas stockée pour des articles alloués statiquement (globaux / piles), ce n'est pas le cas pour les allocations dynamiques. Les allocations dynamiques passent par le gestionnaire de mémoire interne. Nouveau / MALLOC Ne demande pas de nouvelle mémoire à partir du système d'exploitation, il attribue à partir d'un pool existant (qui est étendu si nécessaire). Afin de garder une trace de Mem Mem, un bloc de mémoire alloué est généralement préparé avec des informations de suivi, telles que le pointeur et la taille de bloc suivant. Dans Théorie Supprimer B , ou GRATUIT (B) a toutes les informations nécessaires pour fonctionner correctement.


En outre, le destructeur n'est pas un servallocator. Je ne sais pas pourquoi la norme définit «Supprimer B'ab comportement indéfini, car - comme indiqué par @ben Voigt, Gratuit n'aura aucun problème avec DealLocationg un placement nouveau. Sauf si bien sûr la norme ne déclare une manipulation spéciale pour le nouveau (non-placement) et la suppression. Je n'ai pas lu la norme, mais je ne pense pas que les détails de la mise en œuvre des responsables de la mémoire sont sa chose.



2
votes

Vraisemblablement, votre dernier code est destiné à dire:

Base *b = new Derived;
delete b;  // delete b, not d.


4 commentaires

+1 pour Le fait que la classe ou la structure en question est une pod, une mise en page standard ou de manière triviale ne change rien - en particulier, les classes de présentation de POD / STD semblent être autorisées à avoir des ctors / Les dtors aussi compliqués que vous le souhaitez, et si le DTOR n'est pas virtuel, le temps d'exécution ne peut pas appeler les dtors dérivés.


@Martin: Les classes avec des constructeurs / destructeurs non triviaux ne sont pas trivialement copiables ni pod. S'il vous plaît ne confondez pas la terminologie.


La norme est souvent claire que X est indéfini dans une variété de contextes; Mais il y a d'autres cas où il devient clair, des autres parties de la norme, qu'il n'y a qu'une seule implémentation correcte de Y (où y est un sous-ensemble de x), et donc y est bien défini après tout. (C'est purement théorique, je n'ai pas l'intention de compter sur ce comportement.)


Toute la cosse est là, de sorte que le code CLAvis CLIF qui soit exécuté via un compilateur C ++ ne casse pas inutilement. Évidemment, n'importe quel code qui appelle nouveau et supprime n'est pas un code CLAI ancien.



2
votes

Ceci est signifié comme complément à réponse de Ben VOIGT ', pas de remplacement.

Vous pourriez penser que C'est tout simplement une technicité. Que la norme qui l'appelait «indéfini» est juste un peu de Twadle sémantique qui n'a aucun effet mondial réel au-delà de permettre aux écrivains du compilateur de faire des choses idiotes sans raison. Mais ce n'est pas le cas.

Je pouvais voir des implémentations souhaitables dans lesquelles: xxx

a abouti à un comportement qui était assez bizarre. En effet, stocker la taille de votre morceau de mémoire alloué lorsqu'il est connu de manière statique par le compilateur est un peu idiot. Par exemple: xxx

Dans ce cas, lorsque Supprimer la base est appelé, le compilateur a toutes les raisons (en raison de la règle que vous avez citée au début de votre question) de croire que la taille des données pointées sur est 1, non 4. S'il s'agit, par exemple, implémente une version de opérateur nouveau qui a un tableau séparé dans lequel 1 entités d'octets sont Tous densément emballés et un tableau différent dans lequel 4 entités d'octets sont toutes densément emballées, elle finira par supposer que les points sur quelque part dans la matrice d'entité de 1 octet quand il fait en réalité quelque part dans Tableau d'entité de 4 octets et faisant toutes sortes d'erreurs intéressantes pour cette raison.

Je souhaite vraiment l'opérateur Supprimer avait été défini pour prendre une taille et le compilateur passé dans la taille statique de la taille que si l'opérateur Supprimer a été appelé sur un objet avec un destructeur non virtuel, ou la taille connue de l'objet réel pointé si elle était appelée ul d'un virtuel destructeur. Bien que cela aurait probablement d'autres effets néfastes et que ce soit peut-être une si bonne idée (comme s'il y a des cas dans lesquels l'opérateur Supprimer est appelé sans destructeur ayant été appelé). Mais cela rendrait le problème douloureusement évident.


1 commentaires

N'est-ce pas le fait que n'importe quel type peut avoir surchargé Opérateur neuf () et Supprimer () Raisonnable évident? Bien que je dois admettre que j'aurais aimé obtenir la taille de la taille dans de l'opérateur Suppr () , aussi. Lors de l'utilisation d'allocateurs, la taille appropriée est transmise, un peu en évitant le problème dans de nombreux cas, car des objets sont généralement alloués par une sorte de conteneur de toute façon.