Problème de pointeur avec vector après erase()

Le
none
Bonjour,

Voici un exemple très simplifié de mon problème:

std::vector<int> foo;

foo.push_back(1);
foo.push_back(2);
foo.push_back(3);

int *pun = &foo.at(0);
int *pdeux = &foo.at(1);
int *ptrois = &foo.at(2);

foo.erase(foo.begin() + 1);

Après erase(), *pdeux == 3.

Existe-t-il une solution pour qu'au momment ou erase() est invoqué,
pdeux pointe toujours vers 2 sachant que je ne peux pas réaffecter pun,
pdeux et ptrois explicitement ?

merci pour votre aide.

Xavier
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses Page 1 / 2
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Fabien LE LEZ
Le #304726
On Tue, 27 Mar 2007 22:44:14 +0200, none <""none"@(none)">:

Existe-t-il une solution pour qu'au momment ou erase() est invoqué,
pdeux pointe toujours vers 2


Utiliser std::list<> à la place de std::vector<>.

xavier
Le #304725
Fabien LE LEZ wrote:
On Tue, 27 Mar 2007 22:44:14 +0200, none <""none"@(none)">:

Existe-t-il une solution pour qu'au momment ou erase() est invoqué,
pdeux pointe toujours vers 2


Utiliser std::list<> à la place de std::vector<>.


le problème de std::list<> est que je perds l'indicage que j'utilise
beaucoup dans mon programme.
Remplacer std::vector<int> par std::vector<int*> semble fonctionner,
mais dans mon cas c'est pas très élégant.


Fabien LE LEZ
Le #304724
On Tue, 27 Mar 2007 22:44:14 +0200, none <""none"@(none)">:

foo.erase(foo.begin() + 1);


D'une manière générale, quand tu modifies un vector<>, tu peux
t'attendre à ce que tous les pointeurs et itérateurs sur des éléments
soient invalidés.
(Ce n'est pas toujours le cas, mais ce me semble une mauvaise idée de
prendre le risque.)

xavier
Le #304723
Fabien LE LEZ wrote:
On Tue, 27 Mar 2007 22:44:14 +0200, none <""none"@(none)">:

foo.erase(foo.begin() + 1);


D'une manière générale, quand tu modifies un vector<>, tu peux
t'attendre à ce que tous les pointeurs et itérateurs sur des éléments
soient invalidés.
(Ce n'est pas toujours le cas, mais ce me semble une mauvaise idée de
prendre le risque.)



si j'utilise std::vector<int*>, le pointeur est stocké directement dans
le container, il n'y a donc pas de risque de modification du pointeur, non ?


Fabien LE LEZ
Le #304722
On Tue, 27 Mar 2007 23:11:14 +0200, xavier <xavier>:

le problème de std::list<> est que je perds l'indicage que j'utilise
beaucoup dans mon programme.


Tu perds un indiçage _efficace_.
On peut très bien avoir un indiçage sur std::list<> ; c'est juste plus
lent. Ton profiler te dira si la vitesse reste acceptable pour ton
application.

template <class T> std::list<T>::const_iterator
GetAt (std::list<T> const& tableau, size_t n)
{
std::list<T>::const_iterator reponse= tableau.begin();
for (size_t i=0; i<n; ++i)
{
if (reponse == tableau.end())
// Ici, éventuellement, lancer une exception
++reponse;
}
return reponse;
}

(et la même chose en non-const)


Remplacer std::vector<int> par std::vector<int*> semble fonctionner,
mais dans mon cas c'est pas très élégant.


Tu peux encapsuler tout ça dans une classe.

De toutes façons, si tu utilises beaucoup une structure donnée dans un
programme, il vaut AMHA mieux en faire une classe, que tu pourras
modifier au fur et à mesure de l'évolution de tes besoins.

Une bonne méthode est de commencer par écrire l'interface publique de
la classe, puis de t'occuper des membres privés (données et
implémentation).

Fabien LE LEZ
Le #304721
On Tue, 27 Mar 2007 23:25:00 +0200, xavier <xavier>:

si j'utilise std::vector<int*>, le pointeur est stocké directement dans
le container, il n'y a donc pas de risque de modification du pointeur, non ?


Exact.

xavier
Le #304720
Fabien LE LEZ wrote:
On Tue, 27 Mar 2007 23:11:14 +0200, xavier <xavier>:

le problème de std::list<> est que je perds l'indicage que j'utilise
beaucoup dans mon programme.


Tu perds un indiçage _efficace_.
On peut très bien avoir un indiçage sur std::list<> ; c'est juste plus
lent. Ton profiler te dira si la vitesse reste acceptable pour ton
application.

template <class T> std::list<T>::const_iterator
GetAt (std::list<T> const& tableau, size_t n)
{
std::list<T>::const_iterator reponse= tableau.begin();
for (size_t i=0; i<n; ++i)
{
if (reponse == tableau.end())
// Ici, éventuellement, lancer une exception
++reponse;
}
return reponse;
}

(et la même chose en non-const)


Remplacer std::vector<int> par std::vector<int*> semble fonctionner,
mais dans mon cas c'est pas très élégant.


Tu peux encapsuler tout ça dans une classe.

De toutes façons, si tu utilises beaucoup une structure donnée dans un
programme, il vaut AMHA mieux en faire une classe, que tu pourras
modifier au fur et à mesure de l'évolution de tes besoins.

Une bonne méthode est de commencer par écrire l'interface publique de
la classe, puis de t'occuper des membres privés (données et
implémentation).




Comme j'ai pas mal de code impliqué je vais réflechir un peu avant de
m'orienter vers une solution, merci beaucoup pour ces infos.


Fabien LE LEZ
Le #304719
On Tue, 27 Mar 2007 23:55:08 +0200, xavier <xavier>:

Comme j'ai pas mal de code impliqué je vais réflechir un peu avant de
m'orienter vers une solution


De toutes façons, manifestement, tu as du code qui utilise
vector<int>, et vector<int> ne convient plus, il faut en changer.

Donc, tu devrais remplacer tous les vector<int> par MonTableau<int>,
MonTableau<> étant une classe qui a les fonctions publiques dont tu as
besoin. Tu peux alors changer l'implémentation de MonTableau<> au fur
et à mesure, sans changer son interface, et donc sans casser de code.

James Kanze
Le #305490
On Mar 27, 10:44 pm, none <""none"@(none)"> wrote:

Voici un exemple très simplifié de mon problème:

std::vector<int> foo;

foo.push_back(1);
foo.push_back(2);
foo.push_back(3);

int *pun = &foo.at(0);
int *pdeux = &foo.at(1);
int *ptrois = &foo.at(2);

foo.erase(foo.begin() + 1);

Après erase(), *pdeux == 3.


Après erase(), tout utilisation de pdeux ou de ptrois à un
comportement indéfini. Formellement, tout peut se passer : un
core dump, le reformattage du disque dur, ... (Dans la pratique,
je ne crois pas qu'une implémentation soit possible où *pdeux ne
vaut pas 3, vue les autres contraits sur std::vector.)

Existe-t-il une solution pour qu'au momment ou erase() est invoqué,
pdeux pointe toujours vers 2 sachant que je ne peux pas réaffecter pun,
pdeux et ptrois explicitement ?


Certainement pas, vue qu'après l'erase(), l'élément qui
contenait 2 n'existe plus du tout, nulle part.

En général, après erase() sur un vector, des pointeurs, des
références et des itérateurs à l'élément effacé et à tous l es
éléments suivants sont invalids. Si ça ne convient pas, ce n'est
pas std::vector qu'il te faut.

Dans toutes les collections, après erase(), des pointeurs, des
références et des itérateurs à l'élément effacé sont invalids.
C'est difficile à concevoir comment ça pourrait être autrement ;
comment maintenir un pointeur valid à quelque chose qui n'existe
plus ?

J'ai comme une petite doute que tu dois revoir ta conception,
parce que si ta conception exige des pointeurs à des objets
inexistant, il y a un problème de base.

--
James Kanze (GABI Software) mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

James Kanze
Le #305489
On Mar 27, 10:59 pm, Fabien LE LEZ
On Tue, 27 Mar 2007 22:44:14 +0200, none <""none"@(none)">:

Existe-t-il une solution pour qu'au momment ou erase() est invoqué,
pdeux pointe toujours vers 2


Utiliser std::list<> à la place de std::vector<>.


Même pas. pdeux pointait vers l'élément qu'il a effacé. C'est un
comportement indéfini de s'en servir après erase(), quelque soit
la collection. Dans le cas de std::list<>, il pointera prèsque
sûrement à de la mémoire libérée, ce qui n'est pas très bien non
plus.

--
James Kanze (GABI Software) mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Publicité
Poster une réponse
Anonyme