Puis-je utiliser const dans les vecteurs pour autoriser l’ajout d’éléments, mais pas les modifications apscopes?

Mes commentaires sur cette réponse m’ont amené à réfléchir aux problèmes de la constance et du sorting. J’ai joué un peu et j’ai réduit mes problèmes au fait que ce code:

#include  int main() { std::vector  v; } 

ne comstack pas – vous ne pouvez pas créer un vecteur de const. De toute évidence, j’aurais dû le savoir (et intellectuellement je l’ai fait), mais je n’ai jamais eu besoin de créer une telle chose avant. Cependant, cela me semble une construction utile, et je me demande s’il existe un moyen de contourner ce problème – je veux append des choses à un vecteur (ou autre), mais elles ne devraient pas être modifiées une fois ajoutées.

Il y a probablement une solution assez simple, mais c’est quelque chose que je n’avais jamais envisagé auparavant. Je n’aurais probablement pas dû mentionner le sorting (je peux poser une autre question à ce sujet, voir les difficultés de poser des questions). Mon véritable cas d’utilisation de base est quelque chose comme ceci:

 vector  v; // ok (ie I want it to be OK) v.push_back( 42 ); // ok int n = v[0]; // ok v[0] = 1; // not allowed 

Eh bien, en C ++ 0x, vous pouvez …

En C ++ 03, il y a un paragraphe 23.1 [lib.containers.requirements] / 3, qui dit

Le type d’objects stockés dans ces composants doit répondre aux exigences des types CopyConstructible (20.1.3) et aux exigences supplémentaires des types Assignable .

C’est ce qui vous empêche actuellement d’utiliser const int comme argument de type pour std::vector .

Cependant, en C ++ 0x, ce paragraphe est manquant, au contraire, T doit être Destructible et des exigences supplémentaires sur T sont spécifiées par expression, par exemple v = u sur std::vector n’est valide que si T est MoveConstructible et MoveAssignable .

Si j’interprète correctement ces exigences, il devrait être possible d’instancier std::vector , vous ne manquerez qu’une partie de ses fonctionnalités (ce que je suppose, c’est ce que vous vouliez). Vous pouvez le remplir en passant une paire d’iterators au constructeur. Je pense que emplace_back() devrait fonctionner aussi bien que je n’ai pas réussi à trouver des exigences explicites sur T pour cela.

Cependant, vous ne pourrez toujours pas sortinger le vecteur en place.

Les types que vous mettez dans un conteneur standard doivent être copiables et assignables. La raison pour laquelle auto_ptr cause tant de problèmes est précisément parce qu’elle ne suit pas la sémantique normale de copie et d’affectation. Naturellement, tout ce qui est const ne sera pas assignable. Vous ne pouvez donc rien coller dans un conteneur standard. Et si l’élément n’est pas const , vous allez pouvoir le changer.

Je pense que la solution la plus proche serait d’utiliser une indirection quelconque. Donc, vous pourriez avoir un pointeur sur const ou vous pourriez avoir un object qui contient la valeur que vous voulez, mais la valeur ne peut pas être modifiée dans l’object (comme avec Integer en Java).

Le fait de ne pas pouvoir modifier l’élément à un index donné va à l’encontre du fonctionnement des conteneurs standard. Vous pourriez être en mesure de construire votre propre travail qui fonctionne de cette façon, mais pas les standards. Et aucun basé sur des tableaux ne fonctionnera, sauf si vous parvenez à ajuster leur initialisation dans la syntaxe d’initialisation {a, b, c} car une fois qu’un tableau de const a été créé, vous ne pouvez plus le modifier. Ainsi, une classe vector ne fonctionnera probablement pas avec des éléments const, peu importe ce que vous faites.

Avoir const dans un conteneur sans une sorte d’indirection ne fonctionne tout simplement pas très bien. Vous demandez essentiellement de rendre le conteneur entier const – ce que vous pourriez faire si vous le copiez depuis un conteneur déjà initialisé, mais vous ne pouvez pas vraiment avoir de conteneur – certainement pas un conteneur standard – qui contient des constantes sans aucune sorte de conteneur. indirection.

EDIT : Si ce que vous cherchez à faire est de laisser un conteneur inchangé mais de pouvoir le changer à certains endroits dans le code, alors utilisez une référence const dans la plupart des endroits, puis donnez le code qui doit pouvoir changer l’access direct du conteneur ou une ref non-const rendrait cela possible.

Donc, utilisez const vector& dans la plupart des endroits, puis vector& où vous devez changer le conteneur, ou donnez à cette partie du code un access direct au conteneur. De cette façon, c’est pour la plupart inchangeable, mais vous pouvez le changer quand vous le souhaitez.

D’un autre côté, si vous voulez pouvoir changer à peu près toujours ce qui se trouve dans le conteneur sans modifier des éléments spécifiques, alors je vous suggère de placer une classe wrapper autour du conteneur. Dans le cas de vector , encapsulez-le et faites en sorte que l’opérateur d’index retourne une constante const au lieu d’une référence non-const – soit cela, soit une copie. Donc, en supposant que vous ayez créé une version modélisée, votre opérateur d’indice ressemblerait à ceci:

 const T& operator[](size_t i) const { return _container[i]; } 

De cette façon, vous pouvez mettre à jour le conteneur lui-même, mais vous ne pouvez pas modifier ses éléments individuels. Et tant que vous déclarez toutes les fonctions en ligne, il ne devrait pas y avoir beaucoup de coup de performance (voire pas du tout) d’avoir le wrapper.

Vous ne pouvez pas créer un vecteur de const, et ce serait plutôt inutile même si vous le pouviez. Si je supprime le second int, tout est déplacé vers le bas – read: modified -, ce qui rend impossible de garantir que v [5] a la même valeur à deux occasions différentes.

Ajoutez à cela, un const ne peut pas être assigné après sa déclaration, à moins de supprimer la constance. Et si vous voulez faire cela, pourquoi utilisez-vous const en premier lieu?

Vous allez devoir écrire votre propre cours. Vous pourriez certainement utiliser std :: vector comme votre implémentation interne. Ensuite, implémentez simplement l’interface const et les quelques fonctions non const dont vous avez besoin.

Bien que cela ne réponde pas à toutes vos exigences (pouvoir sortinger), essayez un vecteur constant:

 int values[] = {1, 3, 5, 2, 4, 6}; const std::vector IDs(values, values + sizeof(values)); 

Bien que, vous souhaitiez peut-être utiliser une std::list . Avec la liste, les valeurs n’ont pas besoin de changer, mais uniquement les liens vers celles-ci. Le sorting est effectué en modifiant l’ordre des liens.

Vous devrez peut-être dépenser un peu de cerveau et écrire le vôtre. 🙁

J’aurais tous mes objects const dans un tableau standard.
Ensuite, utilisez un vecteur de pointeurs dans le tableau.
Une petite classe d’utilité juste pour vous aider à ne pas avoir à dé-référencer les objects et le foin presto.

 #include  #include  #include  #include  class XPointer { public: XPointer(int const& data) : m_data(&data) {} operator int const&() const { return *m_data; } private: int const* m_data; }; int const data[] = { 15, 17, 22, 100, 3, 4}; std::vector sorted(data,data+6); int main() { std::sort(sorted.begin(), sorted.end()); std::copy(sorted.begin(), sorted.end(), std::ostream_iterator(std::cout, ", ")); int x = sorted[1]; } 

Je suis avec Noah: enroulez le vecteur avec une classe qui expose uniquement ce que vous voulez autoriser.

Si vous n’avez pas besoin d’append dynamicment des objects au vecteur, considérez std::tr1::array .

Si la constance est importante pour vous dans ce cas, je pense que vous souhaiterez probablement travailler avec des types immuables tout en haut. Conceptuellement, vous aurez une taille fixe, un tableau const de const int s. Chaque fois que vous devez le modifier (par exemple pour append ou supprimer des éléments ou pour sortinger), vous devez faire une copie du tableau avec l’opération effectuée et l’utiliser à la place. Bien que cela soit très naturel dans un langage fonctionnel, cela ne semble pas tout à fait “correct” en C ++. Obtenir des implémentations efficaces, par exemple, peut être délicat – mais vous ne dites pas quelles sont vos exigences de performance. Que vous considériez cette route comme valable d’un sharepoint vue performance / code personnalisé ou non, je crois que c’est la bonne approche.

Après cela, conserver les valeurs par un pointeur / pointeur non-const est probablement le meilleur (mais a bien sûr sa propre surcharge).

J’ai réfléchi un peu à cette question et il semble que vous ayez besoin

Vous ne voulez pas append de valeurs immuables à votre vecteur:

 std::vector vec = /**/; std::vector::const_iterator first = vec.begin(); std::sort(vec.begin(), vec.end()); assert(*vec.begin() == *first); // false, even though `const int` 

Ce que vous voulez vraiment, c’est que votre vecteur contienne une collection constante de valeurs, dans un ordre modifiable, ce qui ne peut pas être exprimé par la syntaxe std::vector même si cela a fonctionné.

Je crains que ce soit une tâche extrêmement précise qui nécessite une classe dédiée.

Il est vrai que Assignable est l’une des exigences standard pour le type d’élément vectoriel et que const int n’est pas assignable. Cependant, je pense que dans une implémentation bien pensée, la compilation ne devrait échouer que si le code repose explicitement sur une affectation. Par exemple, std::vector serait insert et erase .

En réalité, dans de nombreuses implémentations, la compilation échoue même si vous n’utilisez pas ces méthodes. Par exemple, Comeau ne parvient pas à comstackr le fichier std::vector a; car la spécialisation correspondante de std::allocator ne parvient pas à comstackr. Il ne signale aucun problème immédiat avec std::vector lui std::vector même.

Je crois que c’est un problème valide. L’implémentation fournie par la bibliothèque std::allocator est supposée échouer si le paramètre type est qualifié en const. (Je me demande s’il est possible de faire une implémentation personnalisée de std::allocator pour forcer toute la compilation.) (Il serait également intéressant de savoir comment VS parvient à le comstackr) Encore une fois, avec Comeau std::vector ne parvient pas à comstackr pour les mêmes raisons que std::allocator ne comstack pas et, selon la spécification de std::allocator il ne peut pas comstackr.

Bien sûr, dans tous les cas, toute implémentation a le droit de ne pas comstackr std::vector car elle est autorisée à échouer par la spécification du langage.

En utilisant juste un vector non spécialisé, cela ne peut pas être fait. Le sorting est effectué en utilisant l’affectation. Donc, le même code qui rend cela possible:

sort(v.begin(), v.end());

… rend aussi cela possible:

v[1] = 123;

Vous pouvez dériver une classe const_vector à partir de std :: vector qui surcharge toute méthode renvoyant une référence et la renvoyer à la place. Pour faire votre sorting, redissortingbuez à std :: vector.

std::vector de l’object constant échouera probablement à la compilation en raison de l’exigence Assignable , car l’object constant ne peut pas être assigné. La même chose est vraie pour Move Assignment également. C’est aussi le problème que je rencontre fréquemment lorsque je travaille avec une carte vectorielle, telle que boost flat_map ou Loki AssocVector . Comme il a implémentation interne std::vector > . Ainsi, il est presque impossible de suivre l’exigence de la clé const de la carte, qui peut être facilement implémentée pour n’importe quelle carte basée sur un nœud.

Cependant, on peut se demander si std::vector signifie que le vecteur doit stocker un object typé const T ou simplement renvoyer une interface non mutable lors de l’access. Dans ce cas, une implémentation de std::vector est possible qui suit l’exigence Assignable / Move Assignable car elle stocke l’object de type T plutôt que const T Les typedefs et les types d’allocateur standard doivent être légèrement modifiés pour prendre en charge les exigences standard. Pour prendre en charge ces fonctions pour un vector_map ou un flat_map , il faut probablement modifier considérablement l’interface std::pair car elle expose directement les variables membres.

La compilation échoue car push_back() (par exemple) est fondamentalement

 underlying_array[size()] = passed_value; 

où les deux opérandes sont T& . Si T est const X cela ne peut pas fonctionner.

Avoir des éléments const semble avoir raison en principe mais dans la pratique, ce n’est pas naturel, et les spécifications ne disent pas qu’il devrait être supporté, donc il n’y en a pas. Au moins pas dans le stdlib (car alors, il serait dans le vecteur).