std::string de VC++ utilise un buffer interne de 16 octets pour stocker les petites chaines (sur la pile donc) et fait un new si c'est pas suffisant
Ca doit être leur réponse à la suppression du COW.
Je croyais que c'était déjà le cas avant ?
J'ai oublié de dire que c'est la nouvelle std::string de VC++ >= 7 qui fonctionne ainsi (et qui n'utilise plus le COW).
-- Aurélien REGAT-BARREL
Fabien LE LEZ
On Wed, 23 Feb 2005 18:38:27 +0100, dezz :
N'y a-t-il pas un risque à utiliser cette technique
Si, beaucoup. C'est pour ça que c'est compliqué à mettre en oeuvre (surtout en programmation multithread), et que la technique est de plus en plus abandonnée, pour std::string du moins.
(passage par référence d'un objet temporaire, ...) ?
Non, ça ne pose pas problème, car les données internes comportent un compteur du nombre d'objets qui pointent vers ces données.
Si on ne modifie pas l'objet, pourquoi le garder ?
std::string f() { std::string x; return x; }
void g() { std::string y= f(); }
Ici, il y a une copie, très difficile à éviter à moins de changer complètement l'écriture :
void f (std::string& dest) { std::string x; dest= x; }
void g() { std::string y; f(y); }
Dans quels cas, peut-on l'utiliser ?
A priori, tu ne dois pas mettre en place un tel système dans les classes que tu crées.
Si le profiler indique que les copies d'objets de classe C prennent vraiment trop de temps et ralentissent sensiblement le programme,
si tu ne peux vraiment pas éviter les copies en questions (il y a beaucoup de façons d'éviter des copies -- par exemple, utiliser des pointeurs),
si tu es sûr que ta classe ne sera jamais utilisée en multithread (c'est de plus en plus rare),
alors tu peux commencer à envisager d'implémenter COW.
-- ;-)
On Wed, 23 Feb 2005 18:38:27 +0100, dezz <dezz@dezz.net>:
N'y a-t-il pas un risque à utiliser cette technique
Si, beaucoup. C'est pour ça que c'est compliqué à mettre en oeuvre
(surtout en programmation multithread), et que la technique est de
plus en plus abandonnée, pour std::string du moins.
(passage par
référence d'un objet temporaire, ...) ?
Non, ça ne pose pas problème, car les données internes comportent un
compteur du nombre d'objets qui pointent vers ces données.
Si on ne modifie pas l'objet, pourquoi le garder ?
std::string f()
{
std::string x;
return x;
}
void g()
{
std::string y= f();
}
Ici, il y a une copie, très difficile à éviter à moins de changer
complètement l'écriture :
void f (std::string& dest)
{
std::string x;
dest= x;
}
void g()
{
std::string y;
f(y);
}
Dans quels cas, peut-on l'utiliser ?
A priori, tu ne dois pas mettre en place un tel système dans les
classes que tu crées.
Si le profiler indique que les copies d'objets de classe C prennent
vraiment trop de temps et ralentissent sensiblement le programme,
si tu ne peux vraiment pas éviter les copies en questions (il y a
beaucoup de façons d'éviter des copies -- par exemple, utiliser des
pointeurs),
si tu es sûr que ta classe ne sera jamais utilisée en multithread
(c'est de plus en plus rare),
alors tu peux commencer à envisager d'implémenter COW.
N'y a-t-il pas un risque à utiliser cette technique
Si, beaucoup. C'est pour ça que c'est compliqué à mettre en oeuvre (surtout en programmation multithread), et que la technique est de plus en plus abandonnée, pour std::string du moins.
(passage par référence d'un objet temporaire, ...) ?
Non, ça ne pose pas problème, car les données internes comportent un compteur du nombre d'objets qui pointent vers ces données.
Si on ne modifie pas l'objet, pourquoi le garder ?
std::string f() { std::string x; return x; }
void g() { std::string y= f(); }
Ici, il y a une copie, très difficile à éviter à moins de changer complètement l'écriture :
void f (std::string& dest) { std::string x; dest= x; }
void g() { std::string y; f(y); }
Dans quels cas, peut-on l'utiliser ?
A priori, tu ne dois pas mettre en place un tel système dans les classes que tu crées.
Si le profiler indique que les copies d'objets de classe C prennent vraiment trop de temps et ralentissent sensiblement le programme,
si tu ne peux vraiment pas éviter les copies en questions (il y a beaucoup de façons d'éviter des copies -- par exemple, utiliser des pointeurs),
si tu es sûr que ta classe ne sera jamais utilisée en multithread (c'est de plus en plus rare),
alors tu peux commencer à envisager d'implémenter COW.
-- ;-)
dezz
Si on ne modifie pas l'objet, pourquoi le garder ?
std::string f() { std::string x; return x; }
void g() { std::string y= f(); }
Ici, il y a une copie, très difficile à éviter à moins de changer complètement l'écriture :
void f (std::string& dest) { std::string x; dest= x; }
void g() { std::string y; f(y); }
Il n'y a pas de copie dans le 2ème cas ?
A+Z
Si on ne modifie pas l'objet, pourquoi le garder ?
std::string f()
{
std::string x;
return x;
}
void g()
{
std::string y= f();
}
Ici, il y a une copie, très difficile à éviter à moins de changer
complètement l'écriture :
void f (std::string& dest)
{
std::string x;
dest= x;
}
Si on ne modifie pas l'objet, pourquoi le garder ?
std::string f() { std::string x; return x; }
void g() { std::string y= f(); }
Ici, il y a une copie, très difficile à éviter à moins de changer complètement l'écriture :
void f (std::string& dest) { std::string x; dest= x; }
void g() { std::string y; f(y); }
Il n'y a pas de copie dans le 2ème cas ?
A+Z
kanze
Fabien LE LEZ wrote:
On Wed, 23 Feb 2005 18:38:27 +0100, dezz :
N'y a-t-il pas un risque à utiliser cette technique
Si, beaucoup.
Pas vraiment, si on sait ce qu'on fait, et que l'interface est conçue pour le supporter.
C'est pour ça que c'est compliqué à mettre en oeuvre (surtout en programmation multithread), et que la technique est de plus en plus abandonnée, pour std::string du moins.
La raison pourquoi on l'abandonne pour std::string, c'est que l'interface de std::string est mal conçue. En général, mais surtout, elle est mal conçue pour cette technique.
[...]
A priori, tu ne dois pas mettre en place un tel système dans les classes que tu crées.
Ce n'est pas si simple que ça, bien que tu as raison en principe.
Si le profiler indique que les copies d'objets de classe C prennent vraiment trop de temps et ralentissent sensiblement le programme,
si tu ne peux vraiment pas éviter les copies en questions (il y a beaucoup de façons d'éviter des copies -- par exemple, utiliser des pointeurs),
AMHA, c'est en général préférable à limiter la complexité à u ne seule classe. Je préfère implémenter COW dans une classe, bien localiser, que de me forcer de me servir des pointeurs partout, quand la logique du programme ne l'exige pas.
si tu es sûr que ta classe ne sera jamais utilisée en multithread (c'est de plus en plus rare),
alors tu peux commencer à envisager d'implémenter COW.
Il existe de bons algorithmes « lock-free » pour le multi-thread, à condition, évidemment, que l'interface permet à s'en servir. Et même avec un lock -- prendre un lock est souvent moins cher qu'une copie profonde, surtout s'il faut des allocations pour faire la copie profond. (Il est probable que l'allocateur prend un lock.)
-- James Kanze GABI Software 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
Fabien LE LEZ wrote:
On Wed, 23 Feb 2005 18:38:27 +0100, dezz <dezz@dezz.net>:
N'y a-t-il pas un risque à utiliser cette technique
Si, beaucoup.
Pas vraiment, si on sait ce qu'on fait, et que l'interface est
conçue pour le supporter.
C'est pour ça que c'est compliqué à mettre en oeuvre (surtout
en programmation multithread), et que la technique est de plus
en plus abandonnée, pour std::string du moins.
La raison pourquoi on l'abandonne pour std::string, c'est que
l'interface de std::string est mal conçue. En général, mais
surtout, elle est mal conçue pour cette technique.
[...]
A priori, tu ne dois pas mettre en place un tel système dans
les classes que tu crées.
Ce n'est pas si simple que ça, bien que tu as raison en
principe.
Si le profiler indique que les copies d'objets de classe C
prennent vraiment trop de temps et ralentissent sensiblement
le programme,
si tu ne peux vraiment pas éviter les copies en questions
(il y a beaucoup de façons d'éviter des copies -- par exemple,
utiliser des pointeurs),
AMHA, c'est en général préférable à limiter la complexité à u ne
seule classe. Je préfère implémenter COW dans une classe, bien
localiser, que de me forcer de me servir des pointeurs partout,
quand la logique du programme ne l'exige pas.
si tu es sûr que ta classe ne sera jamais utilisée en
multithread (c'est de plus en plus rare),
alors tu peux commencer à envisager d'implémenter COW.
Il existe de bons algorithmes « lock-free » pour le
multi-thread, à condition, évidemment, que l'interface permet à
s'en servir. Et même avec un lock -- prendre un lock est souvent
moins cher qu'une copie profonde, surtout s'il faut des
allocations pour faire la copie profond. (Il est probable que
l'allocateur prend un lock.)
--
James Kanze GABI Software
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
N'y a-t-il pas un risque à utiliser cette technique
Si, beaucoup.
Pas vraiment, si on sait ce qu'on fait, et que l'interface est conçue pour le supporter.
C'est pour ça que c'est compliqué à mettre en oeuvre (surtout en programmation multithread), et que la technique est de plus en plus abandonnée, pour std::string du moins.
La raison pourquoi on l'abandonne pour std::string, c'est que l'interface de std::string est mal conçue. En général, mais surtout, elle est mal conçue pour cette technique.
[...]
A priori, tu ne dois pas mettre en place un tel système dans les classes que tu crées.
Ce n'est pas si simple que ça, bien que tu as raison en principe.
Si le profiler indique que les copies d'objets de classe C prennent vraiment trop de temps et ralentissent sensiblement le programme,
si tu ne peux vraiment pas éviter les copies en questions (il y a beaucoup de façons d'éviter des copies -- par exemple, utiliser des pointeurs),
AMHA, c'est en général préférable à limiter la complexité à u ne seule classe. Je préfère implémenter COW dans une classe, bien localiser, que de me forcer de me servir des pointeurs partout, quand la logique du programme ne l'exige pas.
si tu es sûr que ta classe ne sera jamais utilisée en multithread (c'est de plus en plus rare),
alors tu peux commencer à envisager d'implémenter COW.
Il existe de bons algorithmes « lock-free » pour le multi-thread, à condition, évidemment, que l'interface permet à s'en servir. Et même avec un lock -- prendre un lock est souvent moins cher qu'une copie profonde, surtout s'il faut des allocations pour faire la copie profond. (Il est probable que l'allocateur prend un lock.)
-- James Kanze GABI Software 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
dezz
Fabien LE LEZ wrote:
On Wed, 23 Feb 2005 18:38:27 +0100, dezz :
N'y a-t-il pas un risque à utiliser cette technique
Si, beaucoup.
Pas vraiment, si on sait ce qu'on fait, et que l'interface est conçue pour le supporter.
C'est pour ça que c'est compliqué à mettre en oeuvre (surtout en programmation multithread), et que la technique est de plus en plus abandonnée, pour std::string du moins.
La raison pourquoi on l'abandonne pour std::string, c'est que l'interface de std::string est mal conçue. En général, mais surtout, elle est mal conçue pour cette technique.
[...]
A priori, tu ne dois pas mettre en place un tel système dans les classes que tu crées.
Ce n'est pas si simple que ça, bien que tu as raison en principe.
Si le profiler indique que les copies d'objets de classe C prennent vraiment trop de temps et ralentissent sensiblement le programme,
si tu ne peux vraiment pas éviter les copies en questions (il y a beaucoup de façons d'éviter des copies -- par exemple, utiliser des pointeurs),
AMHA, c'est en général préférable à limiter la complexité à une seule classe. Je préfère implémenter COW dans une classe, bien localiser, que de me forcer de me servir des pointeurs partout, quand la logique du programme ne l'exige pas.
si tu es sûr que ta classe ne sera jamais utilisée en multithread (c'est de plus en plus rare),
alors tu peux commencer à envisager d'implémenter COW.
Il existe de bons algorithmes « lock-free » pour le multi-thread, à condition, évidemment, que l'interface permet à s'en servir. Et même avec un lock -- prendre un lock est souvent moins cher qu'une copie profonde, surtout s'il faut des allocations pour faire la copie profond. (Il est probable que l'allocateur prend un lock.)
-- James Kanze GABI Software 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
Si je comprends bien le principe, on peut arriver à qqc comme ça si le cout d'une copie est trop important :
class A { A& operator=(const A& a1) { if (_tmpAssign != NULL) { _tmpAssign->Release(); _tmpAssign = NULL; } _tmpAssign = &a1; a1.addReference(); }
Cependant, j'ai une question avec le cas suivant : A f() { A a1; return a1; }
void g() { A a2 = f(); //ce qui est retourné par f est un objet temporaire. la référence n'existe plus à la fin de l'appel de f. }
La seule solution que je puisse trouver serait la suivante : A& f() { A* a1 = new A(); return *a1; }
void g() { A a2 = f(); // on a une allocation sur le tas retournée par f(), donc pas de pb. }
C'est dangereux de ne pas pouvoir utiliser le premier cas, à moins que la classe A ne soit une "inner class" ou bien qu'elle ne fasse pas partie de l'interface de la bibliothéque fournie.
Mon raisonnement est correct ?
Merci, Z
Fabien LE LEZ wrote:
On Wed, 23 Feb 2005 18:38:27 +0100, dezz <dezz@dezz.net>:
N'y a-t-il pas un risque à utiliser cette technique
Si, beaucoup.
Pas vraiment, si on sait ce qu'on fait, et que l'interface est
conçue pour le supporter.
C'est pour ça que c'est compliqué à mettre en oeuvre (surtout
en programmation multithread), et que la technique est de plus
en plus abandonnée, pour std::string du moins.
La raison pourquoi on l'abandonne pour std::string, c'est que
l'interface de std::string est mal conçue. En général, mais
surtout, elle est mal conçue pour cette technique.
[...]
A priori, tu ne dois pas mettre en place un tel système dans
les classes que tu crées.
Ce n'est pas si simple que ça, bien que tu as raison en
principe.
Si le profiler indique que les copies d'objets de classe C
prennent vraiment trop de temps et ralentissent sensiblement
le programme,
si tu ne peux vraiment pas éviter les copies en questions
(il y a beaucoup de façons d'éviter des copies -- par exemple,
utiliser des pointeurs),
AMHA, c'est en général préférable à limiter la complexité à une
seule classe. Je préfère implémenter COW dans une classe, bien
localiser, que de me forcer de me servir des pointeurs partout,
quand la logique du programme ne l'exige pas.
si tu es sûr que ta classe ne sera jamais utilisée en
multithread (c'est de plus en plus rare),
alors tu peux commencer à envisager d'implémenter COW.
Il existe de bons algorithmes « lock-free » pour le
multi-thread, à condition, évidemment, que l'interface permet à
s'en servir. Et même avec un lock -- prendre un lock est souvent
moins cher qu'une copie profonde, surtout s'il faut des
allocations pour faire la copie profond. (Il est probable que
l'allocateur prend un lock.)
--
James Kanze GABI Software
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
Si je comprends bien le principe, on peut arriver à qqc comme ça si le
cout d'une copie est trop important :
class A {
A& operator=(const A& a1) {
if (_tmpAssign != NULL)
{
_tmpAssign->Release();
_tmpAssign = NULL;
}
_tmpAssign = &a1;
a1.addReference();
}
Cependant, j'ai une question avec le cas suivant :
A f()
{
A a1;
return a1;
}
void g()
{
A a2 = f(); //ce qui est retourné par f est un objet temporaire.
la référence n'existe plus à la fin de l'appel de f.
}
La seule solution que je puisse trouver serait la suivante :
A& f()
{
A* a1 = new A();
return *a1;
}
void g()
{
A a2 = f(); // on a une allocation sur le tas retournée par f(), donc
pas de pb.
}
C'est dangereux de ne pas pouvoir utiliser le premier cas, à moins que
la classe A ne soit une "inner class" ou bien qu'elle ne fasse pas
partie de l'interface de la bibliothéque fournie.
N'y a-t-il pas un risque à utiliser cette technique
Si, beaucoup.
Pas vraiment, si on sait ce qu'on fait, et que l'interface est conçue pour le supporter.
C'est pour ça que c'est compliqué à mettre en oeuvre (surtout en programmation multithread), et que la technique est de plus en plus abandonnée, pour std::string du moins.
La raison pourquoi on l'abandonne pour std::string, c'est que l'interface de std::string est mal conçue. En général, mais surtout, elle est mal conçue pour cette technique.
[...]
A priori, tu ne dois pas mettre en place un tel système dans les classes que tu crées.
Ce n'est pas si simple que ça, bien que tu as raison en principe.
Si le profiler indique que les copies d'objets de classe C prennent vraiment trop de temps et ralentissent sensiblement le programme,
si tu ne peux vraiment pas éviter les copies en questions (il y a beaucoup de façons d'éviter des copies -- par exemple, utiliser des pointeurs),
AMHA, c'est en général préférable à limiter la complexité à une seule classe. Je préfère implémenter COW dans une classe, bien localiser, que de me forcer de me servir des pointeurs partout, quand la logique du programme ne l'exige pas.
si tu es sûr que ta classe ne sera jamais utilisée en multithread (c'est de plus en plus rare),
alors tu peux commencer à envisager d'implémenter COW.
Il existe de bons algorithmes « lock-free » pour le multi-thread, à condition, évidemment, que l'interface permet à s'en servir. Et même avec un lock -- prendre un lock est souvent moins cher qu'une copie profonde, surtout s'il faut des allocations pour faire la copie profond. (Il est probable que l'allocateur prend un lock.)
-- James Kanze GABI Software 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
Si je comprends bien le principe, on peut arriver à qqc comme ça si le cout d'une copie est trop important :
class A { A& operator=(const A& a1) { if (_tmpAssign != NULL) { _tmpAssign->Release(); _tmpAssign = NULL; } _tmpAssign = &a1; a1.addReference(); }
Cependant, j'ai une question avec le cas suivant : A f() { A a1; return a1; }
void g() { A a2 = f(); //ce qui est retourné par f est un objet temporaire. la référence n'existe plus à la fin de l'appel de f. }
La seule solution que je puisse trouver serait la suivante : A& f() { A* a1 = new A(); return *a1; }
void g() { A a2 = f(); // on a une allocation sur le tas retournée par f(), donc pas de pb. }
C'est dangereux de ne pas pouvoir utiliser le premier cas, à moins que la classe A ne soit une "inner class" ou bien qu'elle ne fasse pas partie de l'interface de la bibliothéque fournie.
Mon raisonnement est correct ?
Merci, Z
Fabien LE LEZ
Evite de citer l'intégralité du message auquel tu réponds. Généralement, deux ou trois lignes (voire zéro) suffisent amplement.
Evite de citer l'intégralité du message auquel tu réponds.
Généralement, deux ou trois lignes (voire zéro) suffisent amplement.
Evite de citer l'intégralité du message auquel tu réponds. Généralement, deux ou trois lignes (voire zéro) suffisent amplement.
James Kanze
dezz wrote:
Si je comprends bien le principe, on peut arriver à qqc comme ça si le cout d'une copie est trop important :
class A { A& operator=(const A& a1) { if (_tmpAssign != NULL) { _tmpAssign->Release(); _tmpAssign = NULL; } _tmpAssign = &a1; a1.addReference(); } private: void addReference() { _count++; } void Release() { count--; if (count == 0} { delete this; } } A* _tmpAssig; int count; }
Tu confonds deux chose. La classe que voit l'utilisateur, et la classe qui contient l'implémentation. Donc, plutôt :
class A { public: A() : myImpl( new Impl ) { myImpl->attach() ; } ~A() { myImpl->detach() ; } A( A const& other ) : myImpl( other.myImpl ) { myImpl->attach() ; } A& operator=( A const& other ) { A tmp( other ) ; swap( tmp ) ; return *this ; } void swap( A& other ) { std::swap( myImpl, other.myImpl ) ; }
private: struct Impl { int refCnt ; // ...
Impl() : refCnt( 0 ) {} Impl( Impl const& other ) : refCnt( 0 ) // !!! // Les autres éléments comme d'habitude... { } void attach() { ++ refCnt ; } void detach() { -- refCnt ; if ( refCnt <= 0 ) { delete this ; } } int count() const { return refCnt ; }
} ;
Impl* myImpl ;
// appelée au début de toute fonction non-const... void prepareToModify() { if ( myImpl->count() > 1 ) { Impl* tmp = new Impl( *myImpl ) ; myImpl->detach() ; myImpl = tmp ; myImpl->attach() ; } } } ;
Bien qu'en général, je trouve le concepte assez intéressant pour avoir un pointeur générique qui le gère. boost::shared_ptr, par exemple (encore que j'ai le mien, qui date de bien avant Boost). Du coup, la version de operator= générée par le compilateur fait
Mon raisonnement est correct ?
Sauf que c'est toujours l'implémentation, et que l'implémentation, qu'on gère par cette technique. Tout son intérêt, c'est que l'utilisateur ne voit rien.
Dans un environement multi-thread, il est possible de le modifier de façon à ce qu'il n'utilise que des primitifs du genre atomicAdd et atomicSub, ou atomicIncr et atomicDecr, voire même CAS directement.
-- James Kanze home: www.gabi-soft.fr Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34
dezz wrote:
Si je comprends bien le principe, on peut arriver à qqc comme
ça si le cout d'une copie est trop important :
class A {
A& operator=(const A& a1) {
if (_tmpAssign != NULL)
{
_tmpAssign->Release();
_tmpAssign = NULL;
}
_tmpAssign = &a1;
a1.addReference();
}
private:
void addReference() {
_count++;
}
void Release() {
count--;
if (count == 0} {
delete this;
}
}
A* _tmpAssig;
int count;
}
Tu confonds deux chose. La classe que voit l'utilisateur, et la
classe qui contient l'implémentation. Donc, plutôt :
class A
{
public:
A()
: myImpl( new Impl )
{
myImpl->attach() ;
}
~A()
{
myImpl->detach() ;
}
A( A const& other )
: myImpl( other.myImpl )
{
myImpl->attach() ;
}
A& operator=( A const& other )
{
A tmp( other ) ;
swap( tmp ) ;
return *this ;
}
void swap( A& other )
{
std::swap( myImpl, other.myImpl ) ;
}
private:
struct Impl
{
int refCnt ;
// ...
Impl() : refCnt( 0 ) {}
Impl( Impl const& other )
: refCnt( 0 ) // !!!
// Les autres éléments comme d'habitude...
{
}
void attach()
{
++ refCnt ;
}
void detach()
{
-- refCnt ;
if ( refCnt <= 0 ) {
delete this ;
}
}
int count() const
{
return refCnt ;
}
} ;
Impl* myImpl ;
// appelée au début de toute fonction non-const...
void prepareToModify()
{
if ( myImpl->count() > 1 ) {
Impl* tmp = new Impl( *myImpl ) ;
myImpl->detach() ;
myImpl = tmp ;
myImpl->attach() ;
}
}
} ;
Bien qu'en général, je trouve le concepte assez intéressant pour
avoir un pointeur générique qui le gère. boost::shared_ptr, par
exemple (encore que j'ai le mien, qui date de bien avant Boost).
Du coup, la version de operator= générée par le compilateur fait
Mon raisonnement est correct ?
Sauf que c'est toujours l'implémentation, et que
l'implémentation, qu'on gère par cette technique. Tout son
intérêt, c'est que l'utilisateur ne voit rien.
Dans un environement multi-thread, il est possible de le
modifier de façon à ce qu'il n'utilise que des primitifs du
genre atomicAdd et atomicSub, ou atomicIncr et atomicDecr, voire
même CAS directement.
--
James Kanze home: www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34
Si je comprends bien le principe, on peut arriver à qqc comme ça si le cout d'une copie est trop important :
class A { A& operator=(const A& a1) { if (_tmpAssign != NULL) { _tmpAssign->Release(); _tmpAssign = NULL; } _tmpAssign = &a1; a1.addReference(); } private: void addReference() { _count++; } void Release() { count--; if (count == 0} { delete this; } } A* _tmpAssig; int count; }
Tu confonds deux chose. La classe que voit l'utilisateur, et la classe qui contient l'implémentation. Donc, plutôt :
class A { public: A() : myImpl( new Impl ) { myImpl->attach() ; } ~A() { myImpl->detach() ; } A( A const& other ) : myImpl( other.myImpl ) { myImpl->attach() ; } A& operator=( A const& other ) { A tmp( other ) ; swap( tmp ) ; return *this ; } void swap( A& other ) { std::swap( myImpl, other.myImpl ) ; }
private: struct Impl { int refCnt ; // ...
Impl() : refCnt( 0 ) {} Impl( Impl const& other ) : refCnt( 0 ) // !!! // Les autres éléments comme d'habitude... { } void attach() { ++ refCnt ; } void detach() { -- refCnt ; if ( refCnt <= 0 ) { delete this ; } } int count() const { return refCnt ; }
} ;
Impl* myImpl ;
// appelée au début de toute fonction non-const... void prepareToModify() { if ( myImpl->count() > 1 ) { Impl* tmp = new Impl( *myImpl ) ; myImpl->detach() ; myImpl = tmp ; myImpl->attach() ; } } } ;
Bien qu'en général, je trouve le concepte assez intéressant pour avoir un pointeur générique qui le gère. boost::shared_ptr, par exemple (encore que j'ai le mien, qui date de bien avant Boost). Du coup, la version de operator= générée par le compilateur fait
Mon raisonnement est correct ?
Sauf que c'est toujours l'implémentation, et que l'implémentation, qu'on gère par cette technique. Tout son intérêt, c'est que l'utilisateur ne voit rien.
Dans un environement multi-thread, il est possible de le modifier de façon à ce qu'il n'utilise que des primitifs du genre atomicAdd et atomicSub, ou atomicIncr et atomicDecr, voire même CAS directement.
-- James Kanze home: www.gabi-soft.fr Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34
dezz
Sauf que c'est toujours l'implémentation, et que l'implémentation, qu'on gère par cette technique. Tout son intérêt, c'est que l'utilisateur ne voit rien.
Dans un environement multi-thread, il est possible de le modifier de façon à ce qu'il n'utilise que des primitifs du genre atomicAdd et atomicSub, ou atomicIncr et atomicDecr, voire même CAS directement.
Merci pour ces explications, je ne connaissais pas cette technique. Je la note dans un coin de ma petite tête, ça pourra servir plus souvent que je ne le pensais.
Cordialement, Z
Sauf que c'est toujours l'implémentation, et que
l'implémentation, qu'on gère par cette technique. Tout son
intérêt, c'est que l'utilisateur ne voit rien.
Dans un environement multi-thread, il est possible de le
modifier de façon à ce qu'il n'utilise que des primitifs du
genre atomicAdd et atomicSub, ou atomicIncr et atomicDecr, voire
même CAS directement.
Merci pour ces explications, je ne connaissais pas cette technique. Je
la note dans un coin de ma petite tête, ça pourra servir plus souvent
que je ne le pensais.
Sauf que c'est toujours l'implémentation, et que l'implémentation, qu'on gère par cette technique. Tout son intérêt, c'est que l'utilisateur ne voit rien.
Dans un environement multi-thread, il est possible de le modifier de façon à ce qu'il n'utilise que des primitifs du genre atomicAdd et atomicSub, ou atomicIncr et atomicDecr, voire même CAS directement.
Merci pour ces explications, je ne connaissais pas cette technique. Je la note dans un coin de ma petite tête, ça pourra servir plus souvent que je ne le pensais.