OVH Cloud OVH Cloud

Comment faire ?

9 réponses
Avatar
Manuel Leclerc
Je voudrais qu'une fonction renvoie un objet encapsulant
un pointeur allou=E9e par la fonction, de fa=E7on =E0 ce que
l'appelant n'ait pas =E0 se soucier de la lib=E9ration du
pointeur : elle sera faite quand l'objet retourn=E9 quitera
la port=E9e dans l'appelant.

exemple

class class amp {
public:
void * m_p;
amp( void * pAllocated ) : m_p( pAllocated ) {}
~amp() { /* lib=E9ration de p */ }
};

amp f( void ) {
/* ... */
return amp( pAllocated );
}

void g( void ) {

amp NoMemoryLeak =3D f();
}

Mon probl=E8me : si je ne d=E9fini pas un
constructeur de copie, la classe devient un peu
dangeureuse. Si je le d=E9fini, n'ai je pas un
probl=E8me de performance important ?
(r=E9allocation / d=E9sallocation "inutiles" au moment
de l'appel de fonction)

9 réponses

Avatar
IR
Manuel Leclerc wrote:
Je voudrais qu'une fonction renvoie un objet encapsulant
un pointeur allouée par la fonction, de façon à ce que
l'appelant n'ait pas à se soucier de la libération du
pointeur : elle sera faite quand l'objet retourné quitera
la portée dans l'appelant.
[snip]


Il y a une raison précise pour ne pas utiliser std::auto_ptr?

--
IR

Avatar
Sylvain Togni
Manuel Leclerc wrote:

class class amp {
public:
void * m_p;
amp( void * pAllocated ) : m_p( pAllocated ) {}
~amp() { /* libération de p */ }
};

amp f( void ) {
/* ... */
return amp( pAllocated );
}

void g( void ) {

amp NoMemoryLeak = f();
}

Mon problème : si je ne défini pas un
constructeur de copie, la classe devient un peu
dangeureuse. Si je le défini, n'ai je pas un
problème de performance important ?
(réallocation / désallocation "inutiles" au moment
de l'appel de fonction)


Regarde du côté de std::auto_ptr ou boost::share_ptr, ils
implémentent des mécanismes permettant d'éviter les copies
inutiles.

Pour une utilisation en retour de fonction, std::auto_ptr
convient, il utilise la notion d'"ownership" pour déterminer
quand désallouer l'objet.

--
Sylvain Togni

Avatar
Manuel Leclerc

Il y a une raison précise pour ne pas utiliser std::auto_ptr?


la ressource que j'encapsule n'a pas été allouée avec new...

Mais du coup je me suis demandé comment auto_ptr prenait
en compte les histoires de copies et de retour de
fonction, et je viens de lire :
http://www.awprofessional.com/content/images/020163371X/autoptrupdate%5Caut o_ptr_update.html

Ca fait peur !

Avatar
Sylvain
Manuel Leclerc wrote on 22/11/2006 18:30:
Je voudrais qu'une fonction renvoie un objet encapsulant
un pointeur allouée par la fonction, de façon à ce que
l'appelant n'ait pas à se soucier de la libération du
pointeur : elle sera faite quand l'objet retourné quitera
la portée dans l'appelant.


cette "simplicité" pour l'appelant peut être une raison, il peut s'agir
également de fournir une facade utilisant une instance interne (m_p) qui
est inconnu de amp ou volontairement masquée.

Mon problème : si je ne défini pas un
constructeur de copie, la classe devient un peu
dangeureuse. Si je le défini, n'ai je pas un
problème de performance important ?


dans les 2 cas, un reference counting sur la classe interne sera
nécessaire, et cela adresse justement les performances (comme le fait
que cette classe interne peut -elle- ne pas supporter la copie).

soit par exemple (je remplace void* par qlq chose de plus expressif)

class class amp {
public:
// public interface for 'amp' service
class iAmp {
private:
long counter;
public:
iAmp() { counter = 0; }
virtual ~iAmp() {}
// referencement
long addRef() { return ++counter; }
long release() { return --counter; }
// service(s)
virtual void foo() = null;
};

protected:
iAmp* m_p; // opaque provider

public:
explicit amp(iAmp* p){
// initialisation par iAmp existant
if ((m_p = p) != null)
m_p->addRef();
}
amp(const amp& other){
// un 'amp' est créé, le même iAmp est utilisé
if ((m_p = other.m_p) != null)
m_p->addRef();
}
amp& operator= (const amp& other){
// utilise le iAmp de 'other', libère le précédent si <>
iAmp* old = m_p;
if ((m_p = other.m_p) != null)
m_p->addRef();
if (old)
old->release();
return *this;
}
virtual ~amp(){
// liberation conditionnel du provider
if (m_p && m_p->release() == 0)
delete m_p;
}

// access aux services
void foo() { if (m_p) m_p->foo(); }
}

notez que l'on détruit (de manière conditionnel) le iAmp dans le
destructeur de amp; on pourrait choisir de le faire dans iAmp::release,
toutefois si m_p est (vraiment) une classe opaque je préfère cette
version, elle évite de plus les risques de destruction alors que la
référence existe encore dans des 'amp' (suite à un mauvais décompte).

Sylvain.

Avatar
dieu.tout.puissant
Manuel Leclerc wrote:

class class amp {
public:
void * m_p;
amp( void * pAllocated ) : m_p( pAllocated ) {}
~amp() { /* libération de p */ }
};

amp f( void ) {
/* ... */
return amp( pAllocated );
}

void g( void ) {

amp NoMemoryLeak = f();
}

Mon problème : si je ne défini pas un
constructeur de copie, la classe devient un peu
dangeureuse. Si je le défini, n'ai je pas un
problème de performance important ?
(réallocation / désallocation "inutiles" au moment
de l'appel de fonction)



Pour le code que tu présentes précisément, la question ne se pose
pas. En effet, si ton compilateur dispose des 2 optimisations (très
répandues) suivantes :
- RVO
- optimisation de l'initilisation d'un objet par un objet temporaire

ton code ne fera pas appel à un constructeur de copie (la construction
de l'objet retourné "amp" tiendra place directement dans l'espace
mémoire alloué à "NoMemoryLeak").

Ensuite, bien entendu, il faut examiner toutes les utilisations
potentielles d'objets de type "amp" , mais il ne vaut mieux pas se
référer à cette notion de classe "un peu dangereuse", soit elle
marche dans tous les cas, soit elle ne marche pas dans certains cas :
c'est ce qui commande la décision d'utiliser ou non un mécanisme de
réplication d'objets sous-jacents.

Avatar
Sylvain
Manuel Leclerc wrote on 22/11/2006 19:58:

Il y a une raison précise pour ne pas utiliser std::auto_ptr?


la ressource que j'encapsule n'a pas été allouée avec new...

Mais du coup je me suis demandé comment auto_ptr prenait
en compte les histoires de copies et de retour de
fonction, et je viens de lire :


"l'histoire de la copie" avec std::auto_ptr est simple, il ne copie pas,
il transfère la propriété (enfin à ce que j'ai compris).

dès lors si le constructeur de copie (amp(const amp&)) ou l'operateur de
copie (operator= (const amp&)) sont utilisés pour effectivement avoir
plusieurs instances qui utilisent le même m_p (et non juste répondre à
comment est stocké le résultat d'un fonction type "amp f();") alors
auto_ptr est inutilisable.

Sylvain.


Avatar
Marc G
je comprends pas trop ton problème...mais j'ai 2 remarques :
- si la ressource n'a pas été allouée avec new dans le fonction f, alors tu
vas avoir de gros problèmes, car tu transmets l'adresse d'un objet
détruit...
- si ta classe amp n'a qu'une donnée membre de type pointeur, un
constructeur de copie ne doit pas être beaucoup moins performant qu'une
initialisation directe !

et si auto_ptr ne te convient pas, tu peux bricoler une classe du genre

template <typename U>
class Ptr
{
public :

//======================================================================================== // Constructeurs
//======================================================================================== Ptr(U* x) : _ptr(x) {} //
prend possession d'un objet U alloué avec new
Ptr(U const& x) : _ptr(new U(x)) {} // copie
intégrale d'un objet U
// transfert de la propriété de l'objet
Ptr(Ptr& x) : _ptr(x.reset()) {} //
"transfert de propriété"
//======================================================================================== ~Ptr() { if (_ptr) delete _ptr;}
U* operator->() throw() { return _ptr;}
U& operator*() throw() { return *_ptr;} //
Opérateur * préfixé
U const* operator->() const throw() { return _ptr;}
U const& operator*() const throw() { return *_ptr;} //
Opérateur * préfixé
U* reset(void) { U* old=_ptr; _ptr=NULL; return old;}
private :
U *_ptr;
};

en faisant attention au constructeur que tu utilises, selon ce que tu
souhaites !
Bon courage
Marc
Avatar
Manuel Leclerc

amp NoMemoryLeak = f();


Pour le code que tu présentes précisément, la question ne se
pose pas. En effet, si ton compilateur dispose des 2 optimisations
(très répandues) suivantes :
- RVO
- optimisation de l'initilisation d'un objet par un objet temporaire

ton code ne fera pas appel à un constructeur de copie (la construction
de l'objet retourné "amp" tiendra place directement dans l'espace
mémoire alloué à "NoMemoryLeak").


Ok. Mon VS2005 n'appelle pas le constructeur de copie en
mode release (il le fait en mode debug).

Il me reste un souci. Je veux pouvoir coder :

bool TestAmp( amp & );
amp NoMemoryLeak;
while ( true ) {
NoMemoryLeak = f();
if ( TestAmp( NoMemoryLeak ) ) break;
}

// NoMemoryLeak utilisable ici

Donc, il va me falloir définir l'opérateur d'affectation.

Ai-je un moyen de savoir que je peux capturer
le pointeur présent dans l'objet rendue par la
fonction f de manière à éviter une séquence
copy/free ?

Autrement dit, puis je définir deux opérateurs
d'affectation différents pour les deux cas ci-dessous

A)
amp NoMemoryLeak;
/* */
NoMemoryLeak = f();

B)
amp NoMemoryLeak;
amp NoMemoryLeak2;
/* */
NoMemoryLeak = NoMemoryLeak2;

Si je ne peux pas, je crois qu'il va falloir que j'utilise
l'histoire du comptage de référence proposée par
Sylvain, mais j'aurais préféré éviter :-)


Avatar
dieu.tout.puissant
On 23 nov, 15:11, "Manuel Leclerc"
wrote:

amp NoMemoryLeak = f();


Pour le code que tu présentes précisément, la question ne se
pose pas. En effet, si ton compilateur dispose des 2 optimisations
(très répandues) suivantes :
- RVO
- optimisation de l'initilisation d'un objet par un objet temporaire

ton code ne fera pas appel à un constructeur de copie (la construction
de l'objet retourné "amp" tiendra place directement dans l'espace
mémoire alloué à "NoMemoryLeak").Ok. Mon VS2005 n'appelle pas le constructeur de copie en
mode release (il le fait en mode debug).


Il me reste un souci. Je veux pouvoir coder :

bool TestAmp( amp & );
amp NoMemoryLeak;
while ( true ) {
NoMemoryLeak = f();
if ( TestAmp( NoMemoryLeak ) ) break;

}// NoMemoryLeak utilisable ici

Donc, il va me falloir définir l'opérateur d'affectation.

Ai-je un moyen de savoir que je peux capturer
le pointeur présent dans l'objet rendue par la
fonction f de manière à éviter une séquence
copy/free ?

Autrement dit, puis je définir deux opérateurs
d'affectation différents pour les deux cas ci-dessous

A)
amp NoMemoryLeak;
/* */
NoMemoryLeak = f();

B)
amp NoMemoryLeak;
amp NoMemoryLeak2;
/* */
NoMemoryLeak = NoMemoryLeak2;

Si je ne peux pas, je crois qu'il va falloir que j'utilise
l'histoire du comptage de référence proposée par
Sylvain, mais j'aurais préféré éviter :-)



Désolé :) mais d'après ces cas d'utilisation, le comptage de
référence est le bon choix. Puisque tu as l'air de vouloir éviter au
maximum les copies non nécessaires, il te faut un mécanisme de
Copy-on-Write. Le comptage de référence est le mécanisme le plus
simple implémentant un CoW.