OVH Cloud OVH Cloud

smart pointer et opérateur delete surchargé

7 réponses
Avatar
bruno.rommens
bonjour,

j'ai codé le smart pointer suivant :

template <typename T> class smartpointer{
protected:
T* m_pT;
public:
smartpointer (T* pT=0):m_pT(pT) {}
~smartpointer ()
{delete(m_pT);m_pT=0;}
T* operator -> () {return(m_pT);}
T& operator * ()
{return(*m_pT);}
operator smartpointer* () {return(this);}
static void operator delete (void* pointer) {}
};

et je voulais savoir ce qui se passe réellement lors de l'exécution de
ce bout de code :

smartpointer<int> sp_int(new int);
delete sp_int;

je sais qu'il y a appel à l'opérateur de cast smartpointer* et ensuite
appel à l'opérateur delete surchargé. mais y a t'il une libération
quelconque de mémoire ?

merci.

7 réponses

Avatar
Frederic Lachasse
"monk31" wrote in message
news:
bonjour,

j'ai codé le smart pointer suivant :

template <typename T> class smartpointer{
protected:
T* m_pT;
public:
smartpointer (T* pT=0):m_pT(pT) {}
~smartpointer ()
{delete(m_pT);m_pT=0;}
T* operator -> () {return(m_pT);}
T& operator * ()
{return(*m_pT);}
operator smartpointer* () {return(this);}
static void operator delete (void* pointer) {}
};

et je voulais savoir ce qui se passe réellement lors de l'exécution de
ce bout de code :

smartpointer<int> sp_int(new int);
delete sp_int;

je sais qu'il y a appel à l'opérateur de cast smartpointer* et ensuite
appel à l'opérateur delete surchargé. mais y a t'il une libération
quelconque de mémoire ?


En fait, il y a:
(1) appel à "operator smartpointer*()" pour convertir sp_int en pointeur
vers sp_int;
(2) appel au destructeur, ce qui:
(2.1) détruit l'objet pointé par m_pT
T étant un int, le destructeur ne fait rien;
(2.2) appelle l' "operator delete(void*)" sur m_pT
m_pT étant un int*, "::operator delete(void*)" est
appelé, ce qui libère la mémoire alloué par le "new int";
(2.3) annulle m_pT;
(3) appel à "operator delete(void*)" de smartpointer<int>
qui ne fait rien.

Ne pas oublier que l'operateur "delete" appelle le destructeur, puis l'
"operator delete(void*)".

De la même façon, l'operateur "new" appelle l' "operator new(size_t)" puis
le constructeur.

--
Frédéric Lachasse -

Avatar
kanze
(monk31) wrote in message
news:...

j'ai codé le smart pointer suivant :

template <typename T> class smartpointer{
protected:
T* m_pT;
public:
smartpointer (T* pT=0):m_pT(pT) {}
~smartpointer ()
{delete(m_pT);m_pT=0;}
T* operator -> () {return(m_pT);}
T& operator * ()
{return(*m_pT);}
operator smartpointer* () {return(this);}
static void operator delete (void* pointer) {}


En passant, la fonction membre operator delete est toujours statique. On
n'a pas le droit de le préciser.

};

et je voulais savoir ce qui se passe réellement lors de l'exécution de
ce bout de code :

smartpointer<int> sp_int(new int);
delete sp_int;

je sais qu'il y a appel à l'opérateur de cast smartpointer* et ensuite
appel à l'opérateur delete surchargé. mais y a t'il une libération
quelconque de mémoire ?


Formellement, c'est un comportement indefini. Lors de l'expression de
delete, on appelle d'abord l'operateur de conversion (comme tu as dit),
puis le destructeur du smartpointer auquel il point, puis le surcharge
de la fonction operator delete. Au moins -- entre le retour du
destructeur et l'appelle à la fonction operator delete, le compilateur a
le droit de faire ce qu'il veut avec la mémoire de l'objet.

Ensuite, quand on quitte la portée de sp_int, on appelle le destructeur
de sp_int de nouveau. Ce qui n'est pas bon.

Si le but est de rendre un delete sur un smartpointer sans effet,
l'opérateur de conversion pourrait renvoyer NULL. Mais c'est trop
subtile pour mes goûts.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16

Avatar
Jean-Marc Bourguet
writes:

En passant, la fonction membre operator delete est toujours statique. On
n'a pas le droit de le préciser.


12.4/6 a l'air d'impliquer qu'on a le droit, et un des "Guidelines"
d'"Exceptional C++" est "Always explicitly declare operator new() and
operator delete() as static functions. They are neve non static member
functions."

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
kanze
"Frederic Lachasse" wrote in message
news:<WJ_Ta.12672$...
"monk31" wrote in message
news:

j'ai codé le smart pointer suivant :

template <typename T> class smartpointer{
protected:
T* m_pT;
public:
smartpointer (T* pT=0):m_pT(pT) {}
~smartpointer ()
{delete(m_pT);m_pT=0;}
T* operator -> () {return(m_pT);}
T& operator * ()
{return(*m_pT);}
operator smartpointer* () {return(this);}
static void operator delete (void* pointer) {}
};

et je voulais savoir ce qui se passe réellement lors de l'exécution
de ce bout de code :

smartpointer<int> sp_int(new int);
delete sp_int;

je sais qu'il y a appel à l'opérateur de cast smartpointer* et
ensuite appel à l'opérateur delete surchargé. mais y a t'il une
libération quelconque de mémoire ?


En fait, il y a:
(1) appel à "operator smartpointer*()" pour convertir sp_int en pointeur
vers sp_int;
(2) appel au destructeur, ce qui:
(2.1) détruit l'objet pointé par m_pT
T étant un int, le destructeur ne fait rien;
(2.2) appelle l' "operator delete(void*)" sur m_pT
m_pT étant un int*, "::operator delete(void*)" est
appelé, ce qui libère la mémoire alloué par le "new int";
(2.3) annulle m_pT;
(3) appel à "operator delete(void*)" de smartpointer<int>
qui ne fait rien.

Ne pas oublier que l'operateur "delete" appelle le destructeur, puis
l' "operator delete(void*)".

De la même façon, l'operateur "new" appelle l' "operator new(size_t)"
puis le constructeur.


C'est vrai, mais tu as oublié l'essentiel dans ce cas-ci : après le
delete ci-dessus, l'objet sp_int contient un pointeur à un objet deleté
(un « dangling pointer », en anglais). Étant donné que sp_int est une
variable locale, son destructeur serait appelé (une deuxième fois) quand
on qui sa portée. Ce qui n'est pas pour arranger des choses.

En fait, la norme parle du comportement indéfini dans ce cas-ci ; une
implémentation de mise au point, par exemple, pourrait générer du code
pour écrire 0xdeadbeef dans tout l'objet à la fin du destructeur. Ce qui
risquera de provoquer une erreur plus évident lors du deuxième appel du
destructeur.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16


Avatar
kanze
Jean-Marc Bourguet wrote in message
news:...
writes:

En passant, la fonction membre operator delete est toujours
statique. On n'a pas le droit de le préciser.


12.4/6 a l'air d'impliquer qu'on a le droit, et un des "Guidelines"
d'"Exceptional C++" est "Always explicitly declare operator new() and
operator delete() as static functions. They are neve non static member
functions."


Hmmm. Tu m'apprends quelque chose. Je crois que tu veux dire §12.5/1,
mais effectivement, l'implication de « even if not explicitly declared
static » est évident.

Je ne sais pas où j'ai eu l'idée qu'on ne pouvait pas les déclarer
static. Normalement, un operator membre ne peut pas être statique
(§13.5/6), mais la paragraphe immédiatement avant dit que ce que dit
cette section ne s'applique pas à operator new et à operator delete. Et
§3.7.3.2 suggère la même chose : « a program is ill-formed if
deallocation functions are [...] declared static in global scope. »

Je n'ai pas ma copie de l'ARM ici pour vérifier. Mais c'est possible
qu'avant la norme, il n'y avait rien que les faisait une exception à la
règle générale qu'une function operator ne peut pas être déclarée
statique.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16


Avatar
Frederic Lachasse
wrote in message
news:
"Frederic Lachasse" wrote in message
news:<WJ_Ta.12672$...
"monk31" wrote in message
news:

j'ai codé le smart pointer suivant :

template <typename T> class smartpointer{
protected:
T* m_pT;
public:
smartpointer (T* pT=0):m_pT(pT) {}
~smartpointer ()
{delete(m_pT);m_pT=0;}
T* operator -> () {return(m_pT);}
T& operator * ()
{return(*m_pT);}
operator smartpointer* () {return(this);}
static void operator delete (void* pointer) {}
};

et je voulais savoir ce qui se passe réellement lors de l'exécution
de ce bout de code :

smartpointer<int> sp_int(new int);
delete sp_int;

je sais qu'il y a appel à l'opérateur de cast smartpointer* et
ensuite appel à l'opérateur delete surchargé. mais y a t'il une
libération quelconque de mémoire ?


En fait, il y a:
(1) appel à "operator smartpointer*()" pour convertir sp_int en
pointeur


vers sp_int;
(2) appel au destructeur, ce qui:
(2.1) détruit l'objet pointé par m_pT
T étant un int, le destructeur ne fait rien;
(2.2) appelle l' "operator delete(void*)" sur m_pT
m_pT étant un int*, "::operator delete(void*)" est
appelé, ce qui libère la mémoire alloué par le "new
int";


(2.3) annulle m_pT;
(3) appel à "operator delete(void*)" de smartpointer<int>
qui ne fait rien.

Ne pas oublier que l'operateur "delete" appelle le destructeur, puis
l' "operator delete(void*)".

De la même façon, l'operateur "new" appelle l' "operator new(size_t)"
puis le constructeur.


C'est vrai, mais tu as oublié l'essentiel dans ce cas-ci : après le
delete ci-dessus, l'objet sp_int contient un pointeur à un objet deleté
(un « dangling pointer », en anglais). Étant donné que sp_int est une
variable locale, son destructeur serait appelé (une deuxième fois) quand
on qui sa portée. Ce qui n'est pas pour arranger des choses.


Et bien non: bruno fait en (2.3) "m_pT = 0" dans son destructeur, donc quand
le destructeur est appelé une deuxième fois, rien ne cassera. Cependant, je
suis d'accord qu'appeler 2 fois un destructeur sur un objet n'est pas propre
du tout. Le risque est très grand que l'on oublie que l'objet est toujours
actif malgrès l'appel à un destructeur (ce qui est contraire aux usages), et
que l'on enlève l'instruction "m_pT = 0", qui est inutile dans un scénario
normal où les destructeurs ne sont appelés qu'une seule fois sur une
instance.

--
Frédéric Lachasse -



Avatar
kanze
"Frederic Lachasse" wrote in message
news:<p8xUa.3128$...
wrote in message
news:
"Frederic Lachasse" wrote in message
news:<WJ_Ta.12672$...
"monk31" wrote in message
news:

j'ai codé le smart pointer suivant :

template <typename T> class smartpointer{
protected:
T* m_pT;
public:
smartpointer (T* pT=0):m_pT(pT) {}
~smartpointer ()
{delete(m_pT);m_pT=0;}
T* operator -> () {return(m_pT);}
T& operator * ()
{return(*m_pT);}
operator smartpointer* () {return(this);}
static void operator delete (void* pointer) {}
};

et je voulais savoir ce qui se passe réellement lors de
l'exécution de ce bout de code :

smartpointer<int> sp_int(new int);
delete sp_int;

je sais qu'il y a appel à l'opérateur de cast smartpointer* et
ensuite appel à l'opérateur delete surchargé. mais y a t'il une
libération quelconque de mémoire ?


En fait, il y a:

(1) appel à "operator smartpointer*()" pour convertir sp_int
en pointeur vers sp_int;

(2) appel au destructeur, ce qui:
(2.1) détruit l'objet pointé par m_pT
T étant un int, le destructeur ne fait rien;
(2.2) appelle l' "operator delete(void*)" sur m_pT
m_pT étant un int*, "::operator delete(void*)" est
appelé, ce qui libère la mémoire alloué par le
"new int";
(2.3) annulle m_pT;
(3) appel à "operator delete(void*)" de smartpointer<int>
qui ne fait rien.

Ne pas oublier que l'operateur "delete" appelle le destructeur,
puis l' "operator delete(void*)".

De la même façon, l'operateur "new" appelle l' "operator
new(size_t)" puis le constructeur.


C'est vrai, mais tu as oublié l'essentiel dans ce cas-ci : après le
delete ci-dessus, l'objet sp_int contient un pointeur à un objet
deleté (un « dangling pointer », en anglais). Étant donné que sp_int
est une variable locale, son destructeur serait appelé (une deuxième
fois) quand on qui sa portée. Ce qui n'est pas pour arranger des
choses.


Et bien non: bruno fait en (2.3) "m_pT = 0" dans son destructeur, donc
quand le destructeur est appelé une deuxième fois, rien ne cassera.


J'avais raté ça. Alors, avec beaucoup de systèmes, ça aurait l'air de
marcher, même s'il a un comportement indéfini. (N'oublions pas qu'après
le destructeur, on n'a que de la mémoire brute, et qu'un compilateur
peut en faire ce qu'il veut, comme par exemple écrire 0xdeadbeef partout
dedans.)

Cependant, je suis d'accord qu'appeler 2 fois un destructeur sur un
objet n'est pas propre du tout.


C'est illégal si le destructeur n'est pas trivial, et que tu n'as rien
fait entre les deux appels pour s'assurer que l'objet peut être
correctement detruit. Le mot opératif ici, c'est « entre les deux
appels ». Ce que tu fais dans le premier destructeur ne compte pas.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16