7
votes

Comment faire face à l'initialisation du membre de référence non Const dans l'objet Const?

Disons que vous avez une classe xxx

Vous pouvez désormais définir une instance Const de cette classe xxx

mais cela échouera En raison du constructeur C (int * v) de non-const contre C (int * v)

de sorte que vous définissez un constructeur const int xxx

mais cela échouera aussi depuis que c Membre "INT * I" est non-const.

Que faire dans de tels cas? Utiliser mutable? Fonderie? Préparez la version constante de la classe?

Edit: Après discussion avec Pavel (ci-dessous), j'ai un peu enquêté sur ce problème. Pour moi, ce que C ++ n'est pas correct. La cible du pointeur doit être un type strict, cela signifie que vous ne pouvez pas par exemple faire ce qui suit: xxx

dans ce cas langage grammaire traite const comme une promesse de ne pas changer la cible du pointeur. De plus, int * consons PTR est une promesse de ne pas modifier la valeur du pointeur lui-même. Ainsi, vous avez deux endroits où const peut être appliqué. Ensuite, vous voudrez peut-être que votre classe modélise un pointeur (pourquoi pas). Et ici, les choses tombent en morceaux. C ++ Grammar fournit des méthodes constantes qui sont capables de promettre de ne pas modifier les valeurs du champ lui-même, mais il n'y a pas de grammaire à souligner que votre méthode ne changera pas de cibles de vos pointes en classe.

Un contournement est de définir deux classes const_c et C par exemple. Ce n'est pas une route royale cependant. Avec des modèles, leurs spécialisations partielles, il est difficile de ne pas coller dans un gâchis. Aussi toutes les variations d'arguments possibles telles que const const_c & arg , const c & arg , const_c & arg , C & Arg Ne pas avoir l'air jolie. Je ne sais vraiment pas quoi faire. Utilisez des classes distinctes ou des const_casts, chaque manoiement semble être faux.

Dans les deux cas, devrais-je marquer des méthodes qui ne modifient pas la cible du pointeur en constante? Ou simplement suivre le chemin traditionnel que la méthode constante ne modifie pas l'état de l'objet lui-même (la méthode constante ne se soucie pas de la cible du pointeur). Ensuite, dans mon cas, toutes les méthodes seraient constituées, car la classe modélise un pointeur ainsi le pointeur lui-même est t * cons . Mais clairement, certains d'entre eux modifient la cible du pointeur et d'autres non.


2 commentaires

mutable ne vous aidera pas ici car cela ne s'applique qu'aux champs eux-mêmes, pas à ce qu'ils pointent.


J'ai l'impression que quelque chose ne correspond pas à la constance correctionnelle. Il doit y avoir une idée fausse quelque part.


7 Réponses :


4
votes

Votre exemple n'échoue pas, k est transmis par la valeur. Le membre i est "implicitement constant" en tant que membres directs de C ne peut pas être modifié lorsque l'instance est constante.
Constance dit que vous ne pouvez pas modifier les membres après l'initialisation, mais les initialiser avec des valeurs de la liste d'initialisation sont bien sûr autorisés - comment voudriez-vous leur donner une valeur?

Qu'est-ce qui ne fonctionne pas, c'est invoquer le constructeur sans le rendre public cependant;)

mise à jour adressant la question mise à jour:

Oui, c ++ vous oblige à vous en quelque sorte parfois, mais conscréness est un comportement standard commun que vous ne pouvez pas simplement redéfinir sans les attentes. Paillis Réponse explique déjà une idiome commune, qui est utilisée dans des bibliothèques éprouvées comme la STL, pour avoir travaillé autour de cette situation.

Parfois, il vous suffit d'accepter que les langues ont des limitations et de traiter toujours les attentes des utilisateurs de l'interface, même si cela signifie appliquer une solution apparemment sous-optimale.


4 commentaires

@Doc: Ensuite, vous pourriez faire const_cast (p) - mais vous devriez réfléchir à deux fois sur ce que vous faites avant de promouvoir les indicateurs de Conscons à la non-constituée.


Je m'excuse vraiment pour mes absends d'esprit. Parce qu'est-ce qui dit Pavel Minaev, je dois inaccepter votre réponse. Après tout, je pense que ce n'est pas une mauvaise question et je n'ai pas trouvé d'explication satisfaisante sur le net.


Vous savez, il y avait une édition dans la question depuis que j'ai répondu, vous pourriez vérifier par vous-même.


Le premier paragraphe est trompeur car l'exemple de la question échoue.



0
votes

Votre question n'a pas de sens. Où avez-vous eu toutes ces prédictions "cela échouera"? Aucun d'entre eux n'est même vrai à distance.

Premièrement, il est complètement pertinent que le paramètre du constructeur soit déclaré const ou non. Lorsque vous passez de valeur (comme dans votre cas), vous pouvez passer un objet const comme argument dans tous les cas, que le paramètre soit déclaré comme const ou non .

Deuxièmement, du point de vue du constructeur, l'objet est pas constante. Indépendamment du type d'objet que vous construisez (constante ou non), à partir du constructeur, l'objet est jamais constante. Donc, il n'y a pas besoin de mutable ou quoi que ce soit.

Pourquoi n'essayez-vous pas simplement de compiler votre code (pour voir que rien ne échouera), au lieu de faire d'étranges prédictions sans arrachettes que quelque chose "échouera"?


1 commentaires

Désolé c'était une erreur. La classe utilise des pointeurs afin qu'il ne passe pas de valeur. J'ai une vraie classe de ce genre, je voulais juste le montrer propre et trop simplifié un exemple.



13
votes

sonne comme si vous voulez qu'un objet qui puisse envelopper soit int * (puis se comporter comme non-const), ou int const * (puis le comportement> const) . Vous ne pouvez pas vraiment le faire correctement avec une seule classe.

En fait, la notion même que Const appliqué à votre classe devrait changer sa sémantique comme celle-ci est fausse - si votre classe modélise un pointeur ou un itérateur (s'il enveloppe un pointeur, il sera probablement le cas), alors const est appliqué à celui-ci ne devrait que signifie que cela ne peut pas être changé lui-même et ne devrait rien impliquer de la valeur indiquée à . Vous devriez envisager de suivre ce que STL fait pour ses conteneurs - c'est précisément pourquoi il a distinct itérateur et const_itéator Const_iterator , avec les deux distincts, mais le premier étant implicitement convertible à la dernière . De plus, dans stl, const itérateur n'est pas identique à celui const_iterator ! Donc, faites simplement la même chose.

[edit] Voici un moyen délicat de réutiliser de la réutilisation maximale entre C const_c assurer l'exactitude de la constance tout au long de la constance et ne pas engraisser dans UB (avec const_cast ): xxx

Si vous avez un peu de champs, il peut être plus facile de les copier dans les deux classes plutôt que d'aller pour une structure . S'il y en a beaucoup, mais ils sont tous du même type, il est plus simple de passer à ce type comme paramètre de type directement, et ne vous embarras pas avec const gabarit d'emballage.


31 commentaires

Oui, tu as raison. Donc, une classe distincte serait la meilleure solution. Est-il conseillé de tout hériter de tout ce que vous devez réécrire tout le code? Considérez C contient des dizaines de points d'auteurs internationaux - alors la version héritée const_c déterrerait des espaces alloués par les membres non-consensés de C. D'autre part, répéter tout le code est un encombrant. Des sucettes?


Le vrai problème avec l'héritage n'est pas la duplication des champs - vous pouvez le refroidir dans une structure, puis appliquer const à cette vente en gros dans votre const_c . Le problème est la réutilisation du code. Tout d'abord, héritage const_c à partir de C n'a pas de sens - vous voulez C pour être implicitement convertible à const_c , pas l'inverse, vous n'avez donc pas à dériver C de const_c si vous souhaitez utiliser héritage pour cela. Mais le problème est que tous les champs que vous héritez de const_c seront également const et mutation c C n'aura rien à travailler. .


Pour la nième fois, je m'excuse pour ma terrible expression. Bien sûr, vous héritez après Const_C, alors considérez que const_c a (peut-être pas des dizaines mais quelques-uns) const ou toutes les données de const-indication qui doivent être attribuées sur la pile. Dans ce cas, il s'agit également d'un problème de classe étendue si elle doit les redéfinir tous à la non-const. Et la réutilisation du code est bien sûr le deuxième problème.


En cas de champs, on peut utiliser mutable. Mais de nombreux documents disent que mutable devrait être utilisé si le champ est logicaly const mais pour une raison quelconque a besoin de mettre à jour. Et ici, il serait mutable par design.


mutable est utilisé lorsque objet est logiquement constant, mais certains de ses champs ne sont pas (par exemple, si lesdits champs servent de cache de tri).


Ok, je peux maintenant me concentrer sur ce sujet "alors const s'appliquer à cela devrait seulement vouloir dire qu'il ne peut pas être changé lui-même et ne peut impliquer quoi que ce soit de la valeur indiquée" je suis en désaccord. Ce serait le cas si Const serait applicable aux champs uniquement et non à des valeurs qu'ils indiquent. Mais ce n'est pas! Et cela apporte une certaine incohérence à la constance. Si la classe est obligée de ne pas se modifier, il me semble logique que cette sémantique devrait affecter récursivement tous les champs. Seulement grâce à l'effet secondaire de la valeur pass par rapport, il n'est pas nécessaire d'écrire très souvent la version de la classe.


Effet secondaire - Je veux dire, la valeur de l'argument de const peut être copiée dans le champ de classe de non-Const.


En C ++, const est peu profond par conception et ne se propage pas à travers des pointeurs et des références. Par exemple, un const auto_ptr vous permet de modifier l'objet qu'il pointe. Si vous souhaitez faire référence à un objet Const, vous le spécifiez séparément, comme dans auto_ptr . Remarque également que const fait s'applique de manière récursive à tous les champs d'un Const de classe / struct (sauf s'il n'est déclaré mutable ) - Mais lorsque de tels champs sont des pointeurs, seuls les pointeurs eux-mêmes sont const , et pas ce qu'ils pointent. Si vous voulez un pointeur de propagation const , il est trivial pour l'écrire vous-même.


> "Mais lorsque de tels champs sont des pointeurs, seuls les pointeurs eux-mêmes sont> const, et non ce qu'ils indiquent" et c'est ce que j'ai appelé incohérence. Parce que la même construction, une fois se comporte de la même manière et une autre fois différente. En fait, il n'est pas appliquer de manière récursive à Tous les champs car le pointeur peut être un champ comme n'importe quel autre. Pour être précis const est profond mais avec le niveau de récursivité défini sur 1. Sinon, il ne sera pas en mesure de faire valoriser le pointeur de valeur indique à Const.


Je pense qu'il y a une certaine confusion ici - const s'applique de manière récursive à une profondeur arbitraire, mais uniquement à champs (et de classes de base), pas à la désarférence du pointeur - depuis un objet référencé Par un pointeur n'est pas logiquement la partie de l'objet contenant ledit pointeur. Donc, si vous avez des structures a , b et c , où un champ in a est de type B et champ dans B est de type C , appliquer const to A propagera tous Le chemin des champs de C , y compris les pointeurs - donc int x deviendra const int x et char * p deviendra char * const p .


Exactement. Et le problème est que les méthodes constantes traitent la Constitution des champs non pointus (y compris les pointeurs) mais non des cibles du pointeur constant comme const char * p . En effet, pour les pointeurs, vous avez deux mondes différents sur lesquels const peut être appliqué. Vous pouvez dire que vous avez: const 1 et const 2. Mais la grammaire de classe peut ne pas gérer facilement Const 1. Considérez ce qui se passerait sans méthodes constantes. Si C ++ fournit des machanismes pour marquer le pointeur cible Const, il devrait fournir la grammaire à gérer cela tout le chemin.


Et comme je l'ai déjà dit, seulement grâce à la nature de copie-valeur des champs non-pointeurs, vous n'avez pas besoin de créer deux versions de classe: const et non-const très souvent. La valeur constante passée au constructeur peut être copiée sur le champ non-Const, puis une instance peut même être marquée comme const ... mais c'est un peu glissant.


Les pointeurs ne sont pas fondamentalement différents de tout type «produit» d'un autre type. Un pointeur est t * et un pointeur de const est t * cons . Le fait que t peut être const u (et que u peut être un type de pointeur v * , et ainsi de suite, AD INFINITUM) n'est pas pertinent au pointeur lui-même. De même, vous avez auto_ptr , et t peut être "code> const u , puis u peut-il lui-même Soyez auto_ptr , et ainsi de suite. La propagation constante n'a pas de sens dans tous les cas d'utilisation, cependant, pas même si nous ne regardons que des pointeurs. Mais vous pouvez toujours écrire votre propre classe d'emballage de pointeur qui le fait.


Oui, je sais que le pointeur peut être T * Const et comment les choses sont. Ce que j'essaie de dire, c'est que cette mise en œuvre est quelque peu laid. Lorsque la solution à ma question, qui est un problème plutôt fondamental, nécessite une programmation de Mody Meta, puis je pense que quelque chose ne va pas dans la construction de la langue; Lorsque PPL a besoin mutable mot-clé, const_casts et ainsi de suite. À mon avis, il serait bien mieux que Const se propagerait sur tous les cibles possibles . Dis-moi pourquoi l'hypothèse que const devrait affecter l'instance de const uniquement dans la manière dont t * cons et non const t * const est meilleur.


Et vous vous trompez que les pointeurs ne sont pas différents de tout autre type. En termes de const, ils sont différents, je pense qu'elles sont une seule et unique construction pour laquelle const peut être appliquée à deux endroits - pour le pointeur lui-même et la valeur qu'elle pointe.


Toutes les autres structures sont affectées par Cons une fois. Avez-vous des méthodes telles que m () const const ? Ce qui pourrait signifier que cette méthode ne change pas l'état de l'objet et, par exemple, il n'allocie pas la mémoire. C'est ainsi que sont les pointeurs.


J'ai déjà expliqué comment les pointeurs ne sont pas différents des modèles de classe. Vous semblez être confus par la syntaxe de pointeur irrégulier; Cependant, en vérité, un type de pointeur est toujours t * [const] [volatile] - il n'y a donc que "un endroit" pour le const. Lorsque vous écrivez quelque chose comme const int * ou int const * , que const a rien à voir avec le pointeur . C'est juste une partie de t , le type auquel il pointe. Ideme va pour les références - const int & n'est pas une "référence de const", c'est une référence à const (vous ne pouvez pas écrire int & const car les références ne sont pas des objets, et sont "toujours const" en tout cas)


De même, par votre logique, vous pouvez dire que pour auto_ptr , il existe "Deux places" pour const- const auto_ptr et auto_ptr . C'est exactement la même chose qu'avec un pointeur brut. Bien sûr, uniquement dans le premier cas, const applied à auto_ptr - Dans le second cas, const est juste une partie d'un spécificateur de type t .


Pourquoi c'est mieux - parce que votre cas n'est pas le seul? Par exemple, imaginez que vous avez deux objets et que le deuxième objet est toujours lié au premier (obtient un pointeur dans son constructeur et ne sera jamais rebondé à un autre objet au cours de sa vie). Ceci est mieux représenté par un pointeur de const (puisque nous ne changerons jamais sa valeur) à un objet non constitué (puisque nous allons changer sa valeur). De manière plus générale, les pointeurs de propagation non constitués sont plus "fondamentaux", car vous pouvez écrire un pointeur de propagation de const-propager en termes de non-constituée d'un, mais non inversement.


Et clarifier mes commentaires précédents. Le problème est que les méthodes constantes, associées à la copie des arguments passés par la valeur, entrent dans des champs tels que const t ou t * cons . Aucun de ces travaux avec const t * const . C'est pourquoi vous devez écrire des classes séparées pour de tels cas. Mon idée de contourner ce problème serait par exemple de fournir des constructeurs constants qui pourraient promouvoir des champs à être constitués (noter que le constructeur de Const ne serait appelé que pour la création d'instances Const).


Re: "J'ai déjà expliqué comment les pointeurs"> non, vous avez tort. Considérez pour l'exemple que vous avez int i . Ensuite, vous faites un pointeur de ce type const int * ptr . Et ensuite pTR = & i . Ce const appartient clairement à PTR et non à la variable, il est indiqué (comme vous pouvez le voir, je suis non-const)


Re: "De même, par votre logique, vous pourriez dire que pour auto_ptr "> dans le premier cas, il appartient à l'instance Auto_PTR (ou autre) dans le deuxième cas, Const affecte l'argument de modèle. Si ce serait par exemple const auto_ptr et cela ferait que ce consiste, alors cela pourrait être ma logique. Mais ici, il est évident pour quiconque const auto_ptr serait une sorte de non-sens. En cas de const t * const Les pointeurs, il n'est pas si facilement visible, mais oui c'est le même genre de non-sens.


Votre point de vue est bien sûr pas mal. Sortez simplement la boîte et réalisez qu'une fois C ++ suppose que const int est un type, et l'autre fois qu'il l'examine comme une "promesse". Semantitique ambiguë au cœur de la langue.


@Doc, désolé, mais vous avez juste tort concernant la syntaxe du pointeur. Veuillez lire la spécification ISO C ++. Dans const int * pTR , const appartient au pointeur, pas le pointeur; Vous écrivez int * const ptr pour l'appliquer au pointeur. Cette dernière syntaxe n'est pas unique au pointeur - vous pouvez également écrire int const x pour déclarer x comme const. Pour int , int Const et const int est identique. Cependant, en général, const se lie "plus serré" que * - donc dans const int * p , const se lie INT , puis * se lie au résultat.


J'accepte également que la syntaxe de déclaration C ++ est généralement horrible, mais pour être honnête, la liaison de const n'est pas la seule chose qui est gâchée - Considérez la différence entre E.G. int * x [] et int (* x) [] pour un autre problème du même genre. Il serait beaucoup plus simple que les types se lissent toujours de gauche à droite et la syntaxe était cohérente pour tous les constructeurs de types, nous pourrions donc écrire quelque chose comme var x: const > ou var x: PTR > , et il serait immédiatement clair qui est ce qui est lequel. Néanmoins, désordonnée comme la syntaxe actuelle est, sa sémantique est la même.


À titre de note latérale, le comportement const peut être observé via Typefs . Par exemple, si vous Typef int *TO_TO_TO_TO_TO_TO_INT , puis écrivez const pTR_TO_INT P , type de variable p sera int * const , et pas const int * .


Revenez à la question actuelle - En vérité, le vrai problème ici n'est pas la non-propagation de la const à travers des pointeurs - cela pourrait être de manière trivialement résolue par une classe de modèle d'emballage qui l'exécute. Comme vous l'avez noté, le problème réel est qu'il n'ya aucun moyen de "surcharger" un constructeur sur la Constance de l'instance construite et que tous les champs d'un "constructeur de const" soient qualifiés de manière appropriée. C'est une notion séparée et je conviens que ce serait très utile (comme votre exemple démontre).


Ils ont deux sous-sens de ce que signifie "Constonee". Une fois, il s'agit d'un type très stict, une autre fois une «promesse» que le pointeur ne changera pas la valeur de la cible. Si ce serait un type strict, alors beaucoup de problèmes seraient partis parce que tout le monde verrait des thig évidentes qui devraient être faites. J'ai beaucoup de problèmes avec mon code à cause de ces pointeurs :( Si vous êtes intéressé, je m'envoie d'envelopper la classe de mathématiques, qui fonctionne sur le pointeur transmis au constructeur. Parfois, j'ai besoin de passer const real * autre Times Real * . J'ai besoin de deux classes pour cela, mais ils font partie de la structure mathémique plus grande ...


... Je travaille sur un nombre indéfini de dimensions et tout cela est juste "un peu sophistiqué"


Vous voudrez peut-être examiner une discussion liée: Stackoverflow.com/questions/ 1822429 / ...


Souhaitez-vous jeter un coup d'œil à ma réponse «mutéeptr» et ce que vous y pensez?



0
votes

a const int * n'est pas la même chose qu'un INT * Const. Lorsque votre classe est constituée, vous avez ce dernier (pointeur constant à un entier mutable). Ce que vous passez est l'ancien (pointeur mutable à entier constant). Les deux ne sont pas interchangeables, pour des raisons évidentes.


0 commentaires

0
votes

Lorsque vous instaniez

const int* whatever = ...;
int* const i = whatever; // error


1 commentaires

J'ai lu des sources de GCC pour les cours d'itérateur et j'ai constaté que ces classes sont si simples qu'ils reproduisent le code pour chaque méthode. Pas mon cas du tout. Notez également que Itérateur ne modélise pas un pointeur, il modélise une bouquet de pointeurs. Dans ma classe de cas consiste toujours à const le pointeur de const (c'est t * cons ), donc je n'ai pas besoin de quatre variantes de const.



0
votes

Ok voici ce que j'ai fait jusqu'à présent. Pour permettre l'héritage après la version constante de la classe sans const_casts ou une surcharge d'espace supplémentaire, j'ai créé un syndicat qui ressemble essentiellement à THS: xxx

lorsque le champ MutaTPTR est déclaré, il se termine de manière à ce que les méthodes Const_ptr est retourné, tandis que les non-consenses deviennent un PTR ordinaire. Il délégue la const-ness de la méthode à la cible du pointeur qui a du sens dans mon cas.

Tous les commentaires?

BTW Vous pouvez bien sûr faire une chose similaire avec des types non-pointeurs, voire des méthodes, Il semble donc que l'introduction de mutable ne soit pas nécessaire (?)


8 commentaires

Le problème avec ceci est qu'il ne fonctionnera pas de manière générale si vous accédez au même mutéportptr en tant que const, puis comme non-const (ce qui se passe quand il s'agit d'un membre d'un objet de const - sera non-constant dans le constructeur, mais const dans d'autres méthodes). La raison pour laquelle il s'agit d'un problème est que, dans C ++, vous ne pouvez lire que sur le membre de l'Union qui a été écrit en dernier - c'est-à-dire si CTOR écrit dans PTR , puis lisez plus tard à partir de const_ptr invoque UB


Il y a une exception à cela si l'Union a deux (ou plusieurs) structures. Si ces structs partagent une "séquence initiale commune" des membres, cette séquence commune peut être accédée à partir de l'une une fois l'une une fois que l'une d'entre elles est attribuée. Pour être "commun", la séquence de champs doit contenir des types "compatibles de mise en page" dans le même ordre. Et tout type t est compatible avec la mise en page avec const t . Donc, si vous enveloppez ces pointeurs dans des structures, il devrait être totalement conforme.


La raison pour laquelle cela ne fonctionnera pas en général (et ne peut pas remplacer mutable pour tous les cas) est dû au fait que vous ne pouvez pas avoir de membres de types avec des constructeurs non triviaux ou des destructeurs des syndicats (par exemple, pas < Code> std :: chaîne ).


D'après ce que j'ai trouvé, vous êtes autorisé à lire de tout membre de l'Union. K & R (Je sais que c'est c) indique que la mise en œuvre est définie. Les normes ouvertes C ++ sont encore plus laconiques. D'autres livres que je ne dis pas beaucoup plus. Je pense que cela fonctionnera sur 99. (9)% compilateurs et pour le reste que j'ai ajouté assert (PTR == cons_ const_ptr) dans le constructeur. Je me demande si les syndicats anonymes de C ++ ne sont pas libres de ces restrictions.


J'ai également constaté que vous pouvez avoir des types avec des constructeurs non triviaux dans les syndicats, vous devez simplement fournir un constructeur, un destructeur, un opérateur d'affectation et un constructeur de copie personnalisés. Voici l'exemple des normes ouvertes C ++, qui contient STD :: String en tant que membre de Union: Union U {int i; flotter f; std :: string s; }. , certaines ou toutes ces fonctions membres doivent être déclarées par l'utilisateur. "


Le problème est bien sûr la mise en page de la mémoire. Le compilateur idiot ou un compilateur à son stade précoce de développement peut mettre en œuvre union sur une structure ordinaire, de sorte que les adresses ne se chevauchent pas.


Dans mon cas, l'autre sens serait d'initialiser les deux: const_ptr et PTR . Comme ils sont des pointeurs, je me soucie de l'endroit où sont-ils posés dans la mémoire, je me soucie seulement de leurs objectifs.


La norme C ++ n'autorise pas la punition de type via un union dans tous les cas, anonyme ou trivial ou autre. Ce n'est pas pertinent ce que c dit que certains utilisateurs C ++ moins informés disent.



0
votes

J'ai rencontré le même problème malheureux et Après avoir déploré le manque de constructeur de const en C ++ , je suis arrivé à la conclusion que deux templatus sont le meilleur parcours, du moins en termes de réutilisation.

une version très simplifiée de mon cas / solution est: p>

 template< typename OutTypeT, typename inTypeT )
 image_cast< shared_ptr<OutTypeT> >( const shared_ptr<InTypeT> & inImage )
 {
     return shared_ptr<OutTypeT>( new OutTypeT( inImage->getData() ) );
 }


0 commentaires