2015-03-08 2 views
6

Ceci est une variante des questions Downcasting using the Static_cast in C++ et Safety of invalid downcast using static_cast (or reinterpret_cast) for inheritance without added memberssécurité de static_cast à pointer-à-classe dérivée de la base destructor

Je ne suis pas clair sur l'expression dans la norme « B qui est en fait un sous-objet d'un objet de type D, le pointeur résultant pointe vers l'objet englobant de type D "par rapport au comportement dans ~ B. Si vous lancez D dans ~ B, est-ce encore un sous-objet à ce moment-là? L'exemple simple suivant montre la question:

void f(B* b); 

class B { 
public: 
    B() {} 
    ~B() { f(this); } 
}; 

class D : public B { public: D() {} }; 

std::set<D*> ds; 

void f(B* b) { 
    D* d = static_cast<D*>(b); // UB or subobject of type D? 
    ds.erase(d); 
} 

Je sais que le casting est une porte ouverte à la catastrophe, et de faire quelque chose comme ça du dtor est une mauvaise idée, mais une revendication collègue « Le code est valide et fonctionne correctement.Cette distribution est parfaitement valide.Le commentaire indique clairement qu'il ne devrait pas être déréférencé ". J'ai souligné que la distribution est inutile et nous devrions préférer la protection fournie par le système de type aux commentaires. Le plus triste, c'est qu'il est l'un des principaux développeurs/développeurs et un "expert" supposé C++. Puis-je lui dire que la distribution est UB?

+2

Je pense que c'est UB, mais je ne suis pas sûr. Cependant, le code [au moins dans cet échantillon] absolu pisse pire que mes chaussettes après deux semaines dans un été chaud sans lavage ... La bonne chose ici serait d'avoir un descacheur dans 'D 'qui a enlevé l'objet de' ds' - cela ne devrait pas être fait dans 'B'. Cela évite bien sûr tout problème avec UB. Le fait que cela puisse effectivement fonctionner et être bien défini est hors de propos. Ou faites 'ds' dans un' std :: set bs' ... –

+0

Tant qu'il n'y a pas d'autres classes dérivées de B et D n'a pas de membres supplémentaires, cela peut fonctionner. Mais le tout sent. Comment pouvez-vous vous assurer que chaque B est un D et si c'est sûr pourquoi auriez-vous des classes différentes en premier lieu? –

+0

@MatsPetersson: Pourquoi pensez-vous que c'est UB? La norme en 5.2.9 donne un exemple presque égal aux OP, bien qu'il utilise des références: 'struct B {}; structure D: public B {}; D d; B & br = d; static_cast (br); 'Bien sûr, ce n'est qu'une question théorique. Le code dans la question de l'OP est horrible. –

Répondre

5

[expr.static.cast]/p11:

Un prvalue de type « pointeur vers CV1B, » où B est un type de classe, peut être converti en un prvalue de type « pointeur vers CV2D », où D est un dérivé classe (article 10) de B, si une conversion de type valide de « pointeur vers D » à « pointeur vers B » existe (4.10), CV2 est le même cv-qualification, ou plus cv-qualification que, CV1 et B est ni une classe de base virtuelle de D ni une classe de base d'une base virtuelle classe de D. La valeur null pointeur (4.10) est convertie à la valeur de pointeur null du type de destination. Si le prvalue de de type « pointeur vers CV1B » points à un B qui est en fait un sous-objet d'un objet de type D, les points de pointeur résultant de l'objet enfermant de type D. Sinon, le comportement n'est pas défini.

La question est donc de savoir si, au moment de la static_cast, le pointeur indique en fait à « une B qui est en fait un sous-objet d'un objet de type D ». Si oui, il n'y a pas d'UB; sinon, le comportement est indéfini que le pointeur résultant soit déréférencé ou non.

[class.dtor]/p15 dit que (Souligné par l'auteur)

Une fois qu'un destructor est invoqué pour un objet, l'objet ne existe

et [base. vie]/p1 dit que

La durée de vie d'un objet de type T se termine lorsque:

  • si T est un type de classe avec un destructor non négligeable (12,4), le début d'appel destructor, ou
  • [...]

De là, alors, la D La durée de vie de l'objet a pris fin dès que son destructeur est appelé, et certainement au moment où le destructeur de B a commencé à s'exécuter - ce qui est après que le corps destructeur de D a terminé son exécution. À ce stade, il n'y a pas "objet de type D" à gauche que ce B peut être un sous-objet de - il "n'existe plus". Ainsi, vous avez UB.

Clang avec UBsan sera report an error sur ce code si B est rendu polymorphe (donné une fonction virtuelle), qui prend en charge cette lecture.

+0

"si une conversion standard valide de" pointeur vers D "vers" pointeur vers B "existe" - veuillez vous référer à 12.7/3: ce qui explique que cette conversion de pointeur est valide tant que "destruction de ces classes n'a pas ** complété ** "et UB otherwhise. Comme ici la desturction de D est terminée et celle de B a commencé, c'est définitivement UB! – Christophe

+0

@Christophe "pointeur vers D" et "pointeur vers B" sont des types, et cette phrase traite des types, pas des valeurs. Une "conversion standard valide de" pointeur vers "D" vers "pointeur vers B" existe "tant que B est une base accessible et non ambiguë de" D "(voir [conv.ptr]/p3). –

+0

Pour ma propre compréhension: Si l'objet n'existe plus après le démarrage du destructeur, pourquoi suis-je autorisé à appeler une fonction de nettoyage à l'intérieur de celui-ci? –

0

Vous devez absolument lui dire que c'est UB! !

Pourquoi?

12,4/7: bases et les membres sont détruits dans l'ordre inverse de l'achèvement de leur constructeur Les objets sont detroyed dans l'ordre inverse de leur constuction.

12.6.2/10: Première (...) classes de base virtuelles sont initialisés (...) puis, classes de base directes sont initialisés

Alors, quand un destructing D, d'abord le D Les membres sont détruits, puis le sous-objet D, et alors seulement B sera détruit.

Ce code fait en sorte que f() est appelée lorsque l'objet B est détruit:

~B() { f(this); } 

Alors, quand un objet D est détruit, le D suboject est détruit d'abord, puis ~ B() est exécutée, appeler f().

En f() vous lancez le pointeur sur un B comme pointeur vers un D. Ce est UB:

3,8/5: (...) après la durée de vie d'un objet a pris fin et avant que le stockage que l'objet occupé est réutilisé ou libéré, tout pointeur qui fait référence à l'emplacement de stockage où l'objet sera ou a été localisé peut être utilisé mais seulement de façon limitée. (...) Le programme a comportement indéfini si le pointeur est utilisé pour accéder à un membre de données non statique ou appeler une fonction membre non statique de l'objet , ou (...) le pointeur est utilisé comme l'opérande d'un static_cast.

+1

"Le lancement statique d'un pointeur ne touchera pas l'objet lui-même, de sorte que cette seule affirmation n'est pas UB dans le sens que rien d'indéfini ne se produira immédiatement." Non, un 'static_cast' peut lui-même être UB. –

+0

En effet, un 'static_cast' sur quelque chose qui n'est pas vraiment de ce type est UB. 'reinterpret_cast' serait OK, tant qu'il n'est pas déréférencé. –

+1

@ T.C. Oups! Tu as complètement raison! Je pensais que seule la derefencing d'un poitner casté était UB, mais suite à votre remarque, j'ai trouvé une citation de la norme qui traite explicitement de la static_casting après la fin de la vie de l'objet. J'ai édité le post: plus de doute: c'est UB! Merci pour l'indice! – Christophe

2

Apparemment, votre collègue a l'impression que tant que vous ne dérérez pas un pointeur invalide, tout va bien. Il est incorrect.

L'évaluation simple d'un tel pointeur a un comportement indéfini. Ce code est évidemment cassé.

+0

Il est défini par l'implémentation, non indéfini, mais le comportement défini par l'implémentation comporte une note de bas de page indiquant "Certaines implémentations peuvent définir que la copie d'une valeur de pointeur incorrecte provoque une erreur d'exécution générée par le système." Cependant, le pointeur ne devient pas invalide tant que la fonction de désallocation n'est pas exécutée. Les pointeurs vers le stockage non initialisé restent des pointeurs valides et le stockage reste disponible jusqu'à l'exécution de la fonction de désallocation. Je pense que la réponse de T.C. donne un argument plus convaincant que le code a néanmoins UB. – hvd