Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Fonction qui supprime les pointeurs d'un container

20 réponses
Avatar
Michael
Bonjour à tous,

dans "Le Language C++" de Stroustrup, il donne l'utilisation suivante de
std::transform:

//H
class foo
{
private:
int i;
public:
foo(int index) : i(index) {}
};

template<class T> T* delete_ptr(T* p)
{
delete p;
return 0;
}

//CPP
std::vector<foo *> v;
v.push_back(new foo(0));
v.push_back(new foo(1));
v.push_back(new foo(2));
v.push_back(new foo(3));

std::transform(v.begin(),v.end(),v.begin(),delete_ptr<foo>);

qui permet de supprimer tous les pointeurs du container...

J'ai créé une fonction Purge à laquelle on passe un vecteur contenant des
pointeurs, et qui fait le transform tout seul.

template<class T> void Purge(std::vector<T*> & liste)
{
std::transform(liste.begin(),liste.end(),liste.begin(),delete_ptr<T>);
}

J'aimerai savoir si pour supporter n'importe quel container il faut que je
fasse une surcharge par container, ou bien si en passant par les template
ça peut se faire, mais j'avoue être un peu charette là dessus...

Comment feriez-vous une telle chose?

10 réponses

1 2
Avatar
xavier
Michael a dit le 13/01/2005 18:18:
J'aimerai savoir si pour supporter n'importe quel container il faut que je
fasse une surcharge par container, ou bien si en passant par les template
ça peut se faire, mais j'avoue être un peu charette là dessus...

Comment feriez-vous une telle chose?


template <typename T, template <typename> class Container>
void Purge(Container<T*> & liste) {
std::transform(liste.begin(),liste.end(),liste.begin(),delete_ptr<T>);
}

xavier

Avatar
kanze
Michael wrote:

dans "Le Language C++" de Stroustrup, il donne l'utilisation suivante
de

std::transform:

//H
class foo
{
private:
int i;
public:
foo(int index) : i(index) {}
};

template<class T> T* delete_ptr(T* p)
{
delete p;
return 0;
}

//CPP
std::vector<foo *> v;
v.push_back(new foo(0));
v.push_back(new foo(1));
v.push_back(new foo(2));
v.push_back(new foo(3));

std::transform(v.begin(),v.end(),v.begin(),delete_ptr<foo>);

qui permet de supprimer tous les pointeurs du container...


Et qui donne un comportement indéfini.

AMHA, ce n'est pas un bon exemple, pour plusieurs raisons.
D'abord, formellement, il donne un comportement indéfini. Dans
la pratique, ce comportement indéfini ne fait jamais de
problème, et est sans importance, dans la mésure où le cible de
transform est la même collection que la source. Dans d'autres
cas, en revanche, on a bien une collection qui contient des
pointeurs supprimés, et donc, la risque d'un comportement
indéfini est élevée. Si on tient à ce que la destination et la
source soient identique, on ne se sert pas de transform, mais de
for_each, et delete_ptr serait plutôt :

template< typename T >
struct delete_ptr
{
void operator()( T*& p )
{
T* tmp = p ;
p = NULL ;
delete tmp ;
}
} ;

J'ai créé une fonction Purge à laquelle on passe un vecteur
contenant des pointeurs, et qui fait le transform tout seul.

template<class T> void Purge(std::vector<T*> & liste)
{

std::transform(liste.begin(),liste.end(),liste.begin(),delete_ptr<T>);

}

J'aimerai savoir si pour supporter n'importe quel container il
faut que je fasse une surcharge par container, ou bien si en
passant par les template ça peut se faire, mais j'avoue être
un peu charette là dessus...

Comment feriez-vous une telle chose?


Avec un template sur le type de la collection :

template< typename Container >
void
purge( Container& c )
{
typedef Container::value_type Ptr ;
BOOST_STATIC_ASSERT( boost::is_pointer< Ptr >::value ) ;
std::for_each( c.begin(), c.end(),
delete_ptr< boost::remove_pointer< Ptr >::type >() ) ;
}

(Enfin, c'est ce que j'aimerais faire. Pour l'instant, je suis
obligé à me servir d'un compilateur qui ne supporte pas Boost.)

--
James Kanze GABI Software http://www.gabi-soft.fr
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

Avatar
kanze
Michael wrote:

dans "Le Language C++" de Stroustrup, il donne l'utilisation suivante
de

std::transform:

//H
class foo
{
private:
int i;
public:
foo(int index) : i(index) {}
};

template<class T> T* delete_ptr(T* p)
{
delete p;
return 0;
}

//CPP
std::vector<foo *> v;
v.push_back(new foo(0));
v.push_back(new foo(1));
v.push_back(new foo(2));
v.push_back(new foo(3));

std::transform(v.begin(),v.end(),v.begin(),delete_ptr<foo>);

qui permet de supprimer tous les pointeurs du container...


Et qui donne un comportement indéfini.

AMHA, ce n'est pas un bon exemple, pour plusieurs raisons.
D'abord, formellement, il donne un comportement indéfini. Dans
la pratique, ce comportement indéfini ne fait jamais de
problème, et est sans importance, dans la mésure où le cible de
transform est la même collection que la source. Dans d'autres
cas, en revanche, on a bien une collection qui contient des
pointeurs supprimés, et donc, la risque d'un comportement
indéfini est élevée. Si on tient à ce que la destination et la
source soient identique, on ne se sert pas de transform, mais de
for_each, et delete_ptr serait plutôt :

template< typename T >
struct delete_ptr
{
void operator()( T*& p )
{
T* tmp = p ;
p = NULL ;
delete tmp ;
}
} ;

J'ai créé une fonction Purge à laquelle on passe un vecteur
contenant des pointeurs, et qui fait le transform tout seul.

template<class T> void Purge(std::vector<T*> & liste)
{

std::transform(liste.begin(),liste.end(),liste.begin(),delete_ptr<T>);

}

J'aimerai savoir si pour supporter n'importe quel container il
faut que je fasse une surcharge par container, ou bien si en
passant par les template ça peut se faire, mais j'avoue être
un peu charette là dessus...

Comment feriez-vous une telle chose?


Avec un template sur le type de la collection :

template< typename Container >
void
purge( Container& c )
{
typedef Container::value_type Ptr ;
BOOST_STATIC_ASSERT( boost::is_pointer< Ptr >::value ) ;
std::for_each( c.begin(), c.end(),
delete_ptr< boost::remove_pointer< Ptr >::type >() ) ;
}

(Enfin, c'est ce que j'aimerais faire. Pour l'instant, je suis
obligé à me servir d'un compilateur qui ne supporte pas Boost.)

--
James Kanze GABI Software http://www.gabi-soft.fr
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

Avatar
Michael
Pourais tu m'expliquer ce code s'il te plait, je ne comprends pas tout:

template< typename T >
struct delete_ptr
{
void operator()( T*& p )
{
T* tmp = p ;
p = NULL ;
delete tmp ;
}
} ;


Là il s'agit d'un foncteur, mais pourquoi faire un delete d'un temporaire?

template< typename Container >
void
purge( Container& c )
{
typedef Container::value_type Ptr ;
Ici je ne connais pas, mais je suppose que Container::value_type correspond

au type d'objets contenus par Container??? (ici un pointeur?)

BOOST_STATIC_ASSERT( boost::is_pointer< Ptr >::value ) ;
Je ne suis pas familier des asserts, mais je suppose que c'est pour

vérifier que le contenu du container est bien un pointeur

std::for_each( c.begin(), c.end(),
delete_ptr< boost::remove_pointer< Ptr >::type >() ) ;


Pourquoi enlever le pointeur ici? A quoi correspond ::type?

}


Merci d'avance!

Avatar
kanze
Michael wrote:
Pourais tu m'expliquer ce code s'il te plait, je ne comprends
pas tout:

template< typename T >
struct delete_ptr
{
void operator()( T*& p )



(Il faudrait y ajouter un const que j' ai oublie.)

{
T* tmp = p ;
p = NULL ;
delete tmp ;
}
} ;


Là il s'agit d'un foncteur, mais pourquoi faire un delete d'un
temporaire?


Parce que techniquement, faire un delete sur un pointeur qui est
contenu dans une collection standard a un comportement
indéfini. Donc, on met le pointeur dans un temporaire, on
remplace avec null dans la collection, et seulement alors on
fait le delete.

Certains aime l'idiome avec swap :

| template< typename T >
| struct delete_ptr
| {
| void operator()( T*& p ) const
| {
| T* tmp = NULL ;
| std::swap( tmp, p ) ;
| delete tmp ;
| }
| } ;

Personnellement, je ne vois pas d'avantage par rapport à ma
version initiale, sauf que c'est plus in.

template< typename Container >
void
purge( Container& c )
{
typedef Container::value_type Ptr ;


Ici je ne connais pas, mais je suppose que
Container::value_type correspond au type d'objets contenus par
Container??? (ici un pointeur?)


Tout à fait. Je dirais que l'utilisation de tous ces typedef's,
c'est la véritable innovation de la STL. À apprendre -- ça
t'étonnerait à quel point c'est utile.

BOOST_STATIC_ASSERT( boost::is_pointer< Ptr >::value ) ;


Je ne suis pas familier des asserts, mais je suppose que c'est
pour vérifier que le contenu du container est bien un pointeur


C'est du Boost (aussi à apprendre). BOOST_STATIC_ASSERT, c'est
un assert qui déclenche lors de la compilation. C-à-d ici is Ptr
n'est pas un pointeur, on aurait une erreur de compilation.

std::for_each( c.begin(), c.end(),
delete_ptr< boost::remove_pointer< Ptr >::type >() ) ;


Pourquoi enlever le pointeur ici? A quoi correspond ::type?


Encore : boost::remove_ptr est une classe générique avec un
typedef d'un type qui s'appelle type. Si la type d'instanciation
est un pointeur, le typedef est le type sans le pointeur.

}



Écrire des choses comme remove_pointer, etc., n'est pas
trivial. Il y a beaucoup de chose à apprendre avant. Les
utiliser, en revanche, n'est pas si compliquer, et vaut la
peine, même si on n'est pas très avancé. (À vrai dire, je ne
crois pas que remove_pointer en soi soit si difficile. Mais
l'ensemble dont il fait partie utilise des techniques qu'il faut
bien qualifier d'avancées.)

--
James Kanze GABI Software http://www.gabi-soft.fr
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


Avatar
Michael
Merci pour toutes ces explications... Néanmoins il y a encore quelque chose
que je ne comprends pas:

Pourquoi passer une référence de pointeur à delete_ptr et pas le pointeur
directement? Le comportement indéfini auquel tu fais allusion en est-il la
seule explication?
Avatar
Michael
xavier wrote in
news:41ecee27$0$7832$:

Michael a dit le 18/01/2005 11:47:
Pourquoi passer une référence de pointeur à delete_ptr et pas le
pointeur directement? Le comportement indéfini auquel tu fais
allusion en est-il la seule explication?


Pour pouvoir modifier la valeur du pointeur dans le conteneur, il faut
le passer par référence.


Alors pourquoi mon premier exemple tiré du bouquin compilait sans
problèmes?


Avatar
xavier
Michael a dit le 18/01/2005 11:47:
Pourquoi passer une référence de pointeur à delete_ptr et pas le pointeur
directement? Le comportement indéfini auquel tu fais allusion en est-il la
seule explication?


Pour pouvoir modifier la valeur du pointeur dans le conteneur, il faut
le passer par référence.

Par contre, j'aimerais savoir en quoi un pointeur invalide dans un
conteneur est un comportement indéfini. A priori, je pensais que le
conteneur lui-même ne pouvait manipuler que les pointeurs, et pas les
déréférencer... La manipulation de pointeur invalide est-elle un
comportement indéfini, tout simplement, ou y a-t-il une raison plus
complexe ?

xavier

Avatar
Michael
OK, c'est très clair maintenant...

Du coup j'en profite pour poser une autre question :-D

*dest++ = operation(*it);

Dans l'ordre, c'est affectation puis incrémentation?

J'imagine que c'est l'inverse si on choisit une pré-incrémentation?
Avatar
xavier
Michael a dit le 18/01/2005 12:18:
Alors pourquoi mon premier exemple tiré du bouquin compilait sans
problèmes?


L'utilisation était très différente.

std::transform() et std::for_each() ne font pas du tout la même chose.

Dans l'esprit, std::transform() fait :

transform(begin, end, dest, operation) {
for(it = begin, it != end; ++it)
*dest++ = operation(*it);
}

et std::for_each() fait :

for_each(begin, end, operation) {
for(it = begin; it != end; ++it)
operation(*it);
}

xavier

1 2