58 votes

Question embarrassante sur les constantes en C++

Mes commentaires sur cette réponse m'a fait réfléchir sur les questions de constance et de tri. J'ai joué un peu et j'ai réduit mes problèmes au fait que ce code :

#include <vector>

int main() {
    std::vector <const int> v;  
}

ne compilera pas - vous ne pouvez pas créer un vecteur de const ints. Évidemment, j'aurais dû le savoir (et intellectuellement, je l'ai su), mais je n'ai jamais eu besoin de créer une telle chose auparavant. Cependant, cette construction me semble utile et je me demande s'il existe un moyen de contourner ce problème. Je veux ajouter des éléments à un vecteur (ou autre), mais ils ne doivent pas être modifiés une fois ajoutés.

Il existe probablement une solution d'une simplicité embarrassante à ce problème, mais c'est quelque chose que je n'avais jamais envisagé auparavant.

Editar: Je n'aurais probablement pas dû mentionner le tri (je poserai peut-être une autre question à ce sujet, cf. ce pour les difficultés à poser des questions). Mon cas d'utilisation de base réel est quelque chose comme ceci :

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

21voto

avakar Points 20031

Eh bien, dans C++0x vous pouvez...

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

Le type d'objets stockés dans ces composants doit répondre aux exigences suivantes CopyConstructible (20.1.3), et les exigences supplémentaires des Assignable types.

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

Cependant, dans C++0x, ce paragraphe est absent, à la place, T doit être Destructible et des exigences supplémentaires sur T sont spécifiés par expression, par exemple v = u sur std::vector n'est valable que si T est MoveConstructible y MoveAssignable .

Si j'interprète correctement ces exigences, il devrait être possible d'instancier std::vector<const int> vous manquerez simplement d'une partie de ses fonctionnalités (ce qui, je suppose, est ce que vous vouliez). Vous pouvez le remplir en passant une paire d'itérateurs au constructeur. Je pense que emplace_back() devrait également fonctionner, bien que je n'aie pas réussi à trouver d'exigences explicites en matière de T pour cela.

Cependant, vous ne serez toujours pas en mesure de trier le vecteur sur place.

17voto

Jonathan M Davis Points 19569

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'il ne suit pas la sémantique normale de copie et d'affectation. Naturellement, tout ce qui est const ne sera pas cessible. Donc, vous ne pouvez pas coller const n'importe quoi dans un conteneur standard. Et si l'élément n'est pas const alors vous sont va être capable de le changer.

La solution la plus proche qui me semble possible serait d'utiliser une sorte d'indirection. Ainsi, vous pourriez avoir un pointeur vers const ou vous pourriez avoir un objet qui contient la valeur que vous voulez mais la valeur ne peut pas être changée dans l'objet (comme vous auriez avec Integer en Java).

Le fait que l'élément à un indice particulier soit immuable va à l'encontre du fonctionnement des conteneurs standard. Vous pourriez être en mesure de construire vos propres conteneurs qui fonctionnent de cette façon, mais les conteneurs standard ne le font pas. Et aucun de ceux qui sont basés sur des tableaux ne fonctionnera de toute façon, à moins que vous ne parveniez à faire entrer leur initialisation dans la section {a, b, c} syntaxe d'initialisation, car une fois qu'un tableau de const a été créé, vous ne pouvez pas le modifier. Ainsi, un vector n'est pas susceptible de fonctionner avec des éléments constants, quoi que vous fassiez.

Avoir const dans un conteneur sans une certaine forme d'indirection ne fonctionne pas très bien. Vous demandez essentiellement à ce que l'ensemble du conteneur const - ce que vous pouvez faire si vous le copiez à partir d'un conteneur déjà initialisé, mais vous ne pouvez pas vraiment avoir un conteneur - certainement pas un conteneur standard - qui contient des constantes sans une sorte d'indirection.

EDITAR : Si ce que vous cherchez à faire est de laisser la plupart du temps un conteneur inchangé mais de pouvoir le modifier à certains endroits du code, alors utiliser une réf const dans la plupart des endroits et donner ensuite au code qui doit pouvoir modifier le conteneur un accès direct ou une réf non const permettrait de le faire.

Donc, utilisez const vector<int>& dans la plupart des endroits, et ensuite soit vector<int>& où vous devez modifier le conteneur, ou donner à cette partie du code un accès direct au conteneur. De cette façon, il est pratiquement invariable, mais vous pouvez le modifier quand vous le souhaitez.

D'un autre côté, si vous voulez être en mesure de toujours modifier ce qui se trouve dans le conteneur, mais pas des éléments spécifiques, je vous suggère de mettre une classe enveloppante autour du conteneur. Dans le cas de vector Il faut donc l'envelopper et faire en sorte que l'opérateur d'indice renvoie une réf. const au lieu d'une réf. non const - soit cela, soit une copie. Ainsi, en supposant que vous ayez créé une version modélisée, votre opérateur d'indice ressemblerait à quelque chose comme 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 inline, l'utilisation du wrapper ne devrait pas avoir de répercussions importantes sur les performances (voire aucune).

10voto

cHao Points 42294

Vous ne pouvez pas créer un vecteur de const ints, et ce serait plutôt inutile même si vous le pouviez. Si j'enlève le deuxième int, tout ce qui se trouve à partir de là est décalé d'une unité vers le bas - lire : modifié - ce qui rend impossible de garantir que v[5] a la même valeur à deux occasions différentes.

De plus, une constante ne peut pas être assignée après avoir été déclarée, à moins de supprimer la constance. Et si vous voulez faire cela, pourquoi utilisez-vous const en premier lieu ?

7voto

Crazy Eddie Points 23778

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

4voto

Thomas Matthews Points 19838

Bien que cela ne réponde pas à toutes vos exigences (être capable de trier), essayez un vecteur constant :

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

Cependant, vous pouvez utiliser un std::list . Avec la liste, les valeurs n'ont pas besoin d'être modifiées, seulement les liens vers celles-ci. Le tri s'effectue en modifiant l'ordre des liens.

Vous devrez peut-être dépenser un peu d'énergie cérébrale et écrire le vôtre. :-(

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X