9
votes

Si la modification d'un objet Const est un comportement indéfini, alors comment les constructeurs et les destructeurs fonctionnent-ils avec accès en écriture?

C ++ standard dit que la modification d'un objet déclaré à l'origine const code> est un comportement indéfini. Mais alors comment fonctionnent les constructeurs et les destructeurs?

class Class {
public:
    Class() { Change(); }
    ~Class() { Change(); }
    void Change() { data = 0; }
private:
    int data;
};

//later:
const Class object;
//object.Change(); - won't compile
const_cast<Class&>( object ).Change();// compiles, but it's undefined behavior


1 commentaires

Vous pouvez également jeter un coup d'œil aux réponses de cette question Stackoverflow.com/ Questions / 755196 / Suppression-A-Cons-Pointer concernant pourquoi il est requis.


5 Réponses :


15
votes

La norme permet explicitement les constructeurs et les destructeurs de traiter les objets const . à partir de 12.1 / 4 "Constructeurs":

Un constructeur peut être invoqué pour un const , ou const volatile objet. ... const et volatile sémantique (7.1.5.1) ne sont pas appliqués sur un objet en construction. Une telle sémantique n'entre en vigueur que lorsque le constructeur de l'objet le plus dérivé (1.8) se termine.

et 12.4 / 2 "destructeurs":

Un destructeur peut être invoqué pour un const , volatile ou const volatile objet. ... const et volatile sémantique (7.1.5.1) ne sont pas appliqués sur un objet sous la destruction. Cette sémantique arrête d'être en vigueur une fois que le destructeur de l'objet le plus dérivé (1.8) commence.

comme arrière-plan, Stroustrup dit dans "Conception et évolution de C ++" (13.3.2 Raffinement de la définition de la définition de Const ):

Pour vous assurer que des objets, mais pas tous, const pourraient être placés la mémoire en lecture seule (ROM), j'ai adopté la règle que tout objet comportant un constructeur (c'est-à-dire une initialisation d'exécution requise ) Ne peut pas être placé dans la ROM, mais d'autres objets const peuvent.

...

Un objet déclaré const est considéré immuable à partir de l'achèvement du constructeur jusqu'au début de son destructeur. Le résultat d'une écriture à l'objet entre ces points est jugé non défini.

Lors de la conception initiale const , je me souviens de discuter que l'idéal const serait un objet qui est écritable jusqu'à ce que le constructeur soit exécuté, puis devient en lecture seule par du matériel. La magie, et enfin sur l'entrée dans le destructeur devient à nouveau enrichie. On pourrait imaginer une architecture marquée qui a effectivement fonctionné de cette façon. Une telle mise en œuvre provoquerait une erreur d'exécution si quelqu'un pouvait écrire sur un objet défini const . D'autre part, quelqu'un pourrait écrire à un objet non défini const qui avait été passé sous forme de référence const ou pointeur. Dans les deux cas, l'utilisateur devra jeter const en premier. L'implication de cette vue est celle de la création const pour un objet qui a été défini à l'origine const , puis écrit à elle est au mieux non définie, alors que de faire la même chose à un objet qui n'a pas été défini à l'origine const est légal et bien défini.

Notez que, avec ce raffinement des règles, la signification de const ne dépend pas de si un type a un constructeur ou non; En principe, ils font tous. Tout objet déclaré const peut maintenant être placé dans ROM, être placé dans des segments de code, être protégé par le contrôle d'accès, etc., afin de vous assurer qu'il ne mutation après avoir reçu sa valeur initiale. Toutefois, une telle protection n'est pas nécessaire, car les systèmes actuels ne peuvent en général pas protéger tous les const de toutes les formes de corruption.


12 commentaires

@Stingraysc: Qu'en est-il de mutable ? MUTABLE Composants de Const Les objets peuvent être modifiés - ces parties ne sont donc pas const .


+1! Je trouve qu'il est encore plus important de dire que «la Semantitique Constitution et volatilité (7.1.5.1) ne sont pas appliquées sur un objet en construction / destruction» dans le même paragraphe.


@LitB: J'ai ajouté ces bits plus importants.


@Michael Burr: Je vais poser que pour la plupart, sinon toutes, les implémentations const ne sont pas attribuées dans la ROM, en particulier non pas le non- mutable parties d'entre eux seul. Pourquoi une mise en œuvre subirait-elle des frais généraux de «verrouillage» des octets de mémoire individuels (pour faire face au boîtier mutable )? Cela peut ne pas être le cas pour les types intrinsèques. La phrase clé est "const sémantique ". C'est une construction logique appliquée par le compilateur sur aide Garantie de la correction de la correction, rien de plus.


@Stingraysc: Vous avez raison pour laquelle une implémentation ne doit pas nécessairement mettre un objet const en mémoire en lecture seule (ou la mémoire pouvant être commutée pour être en lecture seule). Je ne pensais pas que c'est ce que je disais; Cependant, Stroustrup envisageait que cela pourrait être possible et que cela a permis de se produire si une mise en œuvre pourrait le retirer. La partie la plus importante de la réponse (même si ce n'est pas la partie la plus longue) est que la norme permet à CTORS et à DTors d'agir sur des objets const . Le comportement est tel que l'objet n'allait pas ' const ' jusqu'à ce que la CTOR soit terminée et cesse d'être 'const ' pour le DTOR.


Cela me semble assez intuitif; Dans le même sens qu'un int ne va pas const jusqu'à ce qu'il soit initialisé. Le boîtier destructeurs est spécial bien sûr, mais il devrait rarement être nécessaire de modifier la mémoire de l'objet pendant la destruction, non? (Réduction destructeurs amateurs qui pensent avoir besoin de zéro les membres ou à une autre chose). Mais il est certainement logique que le const d'un objet se termine au début du destructeur. L'objet n'est pas utilisable après ce point et la mémoire est sur le point d'être libérée.


@Stingraysc: Je ne considère pas ces amateurs. J'envisageai la mise à zéro des pointeurs dans un destructeur d'être une bonne programmation défensive. Cette pratique peut attraper l'utilisation d'un objet à travers un pointeur erroné après la destruction d'une manière relativement sûre et évidente à travers un segfault par opposition à l'attente de la mémoire à corrompre.


@Omnifarious: Eh bien, vous allez avoir la faute SEG que vous nulez ou non la mémoire! Vous allez devoir essayer d'expliquer cela à nouveau ...


@Stingraysc: Lorsque vous accédez à la mémoire de l'objet mort et suivez l'un de ses pointeurs, vous obtiendrez une erreur de segmentation si ce pointeur a été mis à zéro lorsque l'objet a été détruit.


@Omnifarious: Vous obtiendrez probablement une faute SEG accéder à la mémoire de l'objet d'abord en premier lieu. Deuxièmement, si vous êtes religieusement des pointeurs de zéro sur les destructeurs, il n'est même pas possible d'accéder à la mémoire de cet objet de mort Premier , sauf si vous avez un design horrible en premier lieu!


Il serait intéressant et cool Si un compilateur pouvait exécuter des formes de constructeurs pendant la phase de compilation et écrire l'objet construit dans la mémoire en lecture seule.


@Zanlynx: Je suppose que avec C ++ 11 consexpr , de bons compilateurs devraient faire exactement cela.



0
votes

Constenness pour un type défini par l'utilisateur est différent de la Constance d'un type intégré. Constenness lorsqu'il est utilisé avec des types définis par l'utilisateur, est dit "Constance logique". Le compilateur applique que seules les fonctions membres déclarées "Cons" peuvent être appelées objet (ou pointeur, ou référence). Le compilateur ne peut pas allouer l'objet dans une zone de mémoire en lecture seule, car les fonctions membres non-const doit pouvoir modifier l'état de l'objet (et même const les fonctions membres doivent être capable de quand une variable de membre est déclarée mutable ).

Pour les types intégrés, je pense que le compilateur est autorisé à allouer l'objet en lecture seule (si la plate-forme prend en charge une telle chose). Donc, je jette la const et la modification de la variable pourrait entraîner une défaillance de protection de la mémoire de course.


0 commentaires

1
votes

La norme n'en dis pas vraiment sur la manière dont la mise en œuvre le fait fonctionner, mais l'idée de base est assez simple: le const s'applique à l'objet , pas (nécessairement) à la mémoire dans laquelle l'objet est stocké. Puisque le CTOR fait partie de ce qui crée l'objet, ce n'est pas vraiment un objet avant (peu de temps après), le CTOR revient. De même, puisque le DTOR participe à la destruction de l'objet, il ne fonctionne plus vraiment sur un objet complet non plus.


0 commentaires

1
votes

Voici une façon d'ignorer la norme pourrait entraîner un comportement incorrect. Considérons une situation comme celle-ci:

void cheat(const Value &cv)
{
     Value &v = const_cast<Value &>(cv);
     v.set(v.get() + 2);
}


2 commentaires

Pouvez-vous nommer une plate-forme dans laquelle ce code ne fonctionnera pas correctement?


Le comportement indéfini de @Hkbattousai IIT, alors réfléchissant aux spécificités de l'endroit où il pourrait arriver de faire ce que vous pensez être "approprié" (ce qui est?) est inutile. Les termes tels que «correctement» perdent toute la signification lorsque UB est impliqué: il n'ya aucun moyen de dire quel résultat de drawit () serait correct, car le code est fondamentalement brisé et auto-contradictoire. C'est pourquoi UB est un concept.



2
votes

Élaborer sur ce que Jerry Coffin a déclaré: la norme permet d'accéder à un objet de const indéfini, uniquement si cet accès se produit pendant la durée de vie de l'objet.

7.1.5.1/4:

Sauf que tout membre de classe déclaré mutable (7.1.1) peut être modifié, toute tentative de modification d'un objet Const au cours de sa durée de vie (3.8) entraîne un comportement non défini.

La durée de vie de l'objet commence qu'après la fin du constructeur.

3.8 / 1:

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

  • Stockage avec l'alignement et la taille appropriés pour le type T est obtenu et
  • Si T est un type de classe avec un constructeur non trivial (12.1), l'appel du constructeur est terminé.

0 commentaires