|
| C++ og refcounting Fra : Soeren Sandmann |
Dato : 11-03-01 22:53 |
|
Er nedenstående fornuftigt hvis man har sat sig i hovedet at man vil
have refcounting på sine objekter? Jeg tænker specielt på om
'delete this' er sundt.
Man skal naturligvis være forsigtig med objekter der er allokeret på
stakken. Det er jeg klar over.
class C {
int ref_count;
public:
C (void) { ref_count = 1; }
virtual void ref () {
ref_count++;
};
virtual void unref () {
if (--ref_count == 0)
delete this;
}
virtual ~C (void) {}
};
| |
Christian Worm Morte~ (11-03-2001)
| Kommentar Fra : Christian Worm Morte~ |
Dato : 11-03-01 23:29 |
|
Hej,
> Er nedenstående fornuftigt hvis man har sat sig i hovedet at man vil
> have refcounting på sine objekter? Jeg tænker specielt på om
> 'delete this' er sundt.
Det er den ikke - dermed vil du jo komme til at udføre kode i et objekt der
er slettet... Måden at gøre det på er formodentlig at lave en særlig
pegertype som tæller ref_count på objektet op når den blev konstrueret og
ned når den blev dekonstrueret. Hvis ref_count så blev talt ned til 0 kunne
den så slette det objekt den peger på.
Venlig Hilsen
Christian Worm
| |
Soeren Sandmann (12-03-2001)
| Kommentar Fra : Soeren Sandmann |
Dato : 12-03-01 13:41 |
|
"Christian Worm Mortensen" <worm@dkik.dk> writes:
> > 'delete this' er sundt.
>
> Det er den ikke - dermed vil du jo komme til at udføre kode i et objekt der
> er slettet...
Men der bliver jo ikke udført mere kode i objektet; 'delete this' er
det sidste der sker, i hvert fald hvis unref() ikke bliver
overskrevet.
| |
Mogens Hansen (12-03-2001)
| Kommentar Fra : Mogens Hansen |
Dato : 12-03-01 20:41 |
|
Hej Søren,
"Soeren Sandmann" <sandmann@daimi.au.dk> wrote in message
news:ye8lmqbw45x.fsf@rangerover.daimi.au.dk...
> "Christian Worm Mortensen" <worm@dkik.dk> writes:
>
> > > 'delete this' er sundt.
> >
> > Det er den ikke - dermed vil du jo komme til at udføre kode i et objekt
der
> > er slettet...
>
> Men der bliver jo ikke udført mere kode i objektet; 'delete this' er
> det sidste der sker, i hvert fald hvis unref() ikke bliver
> overskrevet.
Lad være med at lave den virtual, så signalerer du at det ikke er meningen
at den skal overskrives.
Venlig hilsen
Mogens Hansen
| |
Christian Worm Morte~ (12-03-2001)
| Kommentar Fra : Christian Worm Morte~ |
Dato : 12-03-01 20:41 |
|
Hej,
> > > 'delete this' er sundt.
> >
> > Det er den ikke - dermed vil du jo komme til at udføre kode i et objekt
der
> > er slettet...
>
> Men der bliver jo ikke udført mere kode i objektet; 'delete this' er
> det sidste der sker, i hvert fald hvis unref() ikke bliver
> overskrevet.
Nej, jeg vil gerne tro på at det i praksis kan virke. Men det er jo ikke det
samme som at det er tilladt. Ok, jeg har ikke viden nok om C++ til med
sikkerhed at kunne sige at det er tilladt eller ikke er tilladt - men det
ville efter min mening være ganske grimt hvis det var..
Venlig Hilsen
Christian Worm
| |
Søren og Rikke Mors (12-03-2001)
| Kommentar Fra : Søren og Rikke Mors |
Dato : 12-03-01 17:34 |
|
Soeren Sandmann wrote:
>
> Er nedenstående fornuftigt hvis man har sat sig i hovedet at man vil
> have refcounting på sine objekter? Jeg tænker specielt på om
> 'delete this' er sundt.
Hej Søren, det er længe siden vi sås sidst.
Den ganske udmærkede c++ faq på:
http://marshall-cline.home.att.net/cpp-faq-lite/freestore-mgmt.html#[16.14]
mener at delete this er sundt nok, men skal anvendes med forsigtighed.
[16.14] Is it legal (and moral) for a member function to say delete
this?
As long as you're careful, it's OK for an object to commit suicide
(delete this).
Here's how I define "careful":
1.You must be absolutely 100% positive sure that this object was
allocated via new (not by new[], nor by placement new, nor a local
object on the stack, nor a global, nor a member of another object; but
by plain ordinary new).
2.You must be absolutely 100% positive sure that your member function
will be the last member function invoked on this object.
3.You must be absolutely 100% positive sure that the rest of your
member function (after the delete this line) doesn't touch any piece of
this object (including calling any other member functions or touching
any data members).
4.You must be absolutely 100% positive sure that no one even touches
the this pointer itself after the delete this line. In other words, you
must not examine it, compare it with another pointer, compare it with
NULL, print it, cast it, do anything with it.
Naturally the usual caveats apply in cases where your this pointer is a
pointer to a base class when you don't have a virtual destructor.
Søren Mors
| |
Mogens Hansen (12-03-2001)
| Kommentar Fra : Mogens Hansen |
Dato : 12-03-01 20:41 |
|
Hej Søren,
"Soeren Sandmann" <sandmann@daimi.au.dk> wrote in message
news:ye8y9ucvuqg.fsf@rangerover.daimi.au.dk...
> Er nedenstående fornuftigt hvis man har sat sig i hovedet at man vil
> have refcounting på sine objekter? Jeg tænker specielt på om
> 'delete this' er sundt.
Det kan lade sig gøre, hvis man er forsigtig.
>
> Man skal naturligvis være forsigtig med objekter der er allokeret på
> stakken. Det er jeg klar over.
>
> class C {
> int ref_count;
> public:
> C (void) { ref_count = 1; }
> virtual void ref ()
> ref_count++;
> };
> virtual void unref () {
> if (--ref_count == 0)
> delete this;
> }
> virtual ~C (void) {}
> };
Tilbage står der
* at det er brugerens opgave at kalde "ref" og "unref" rigtigt - også i
forbindelse med fejlhåndtering.
* at det er brugerens opgave at sikre at det er et dynamisk allokeret -
som du også siger
Hvis du laver en counted pointer (se f.eks. "Advanced C++, programming
styles and idioms", James O. Coplien, side 65) og tvinger brugeren til at
bruge den (ved ikke at kunne kalde "ref" og "unref" direkte), kan du simpelt
undgå mange fejl - f.eks. i forbindelse med fejlhåndtering ved hjælp af
exceptions.
Hvis du samtidig gør destructoren til din klasse protected, og klassen er
friend med din counted pointer, sikrer du at dit objekt kun kan allokeres på
heapen og kun kan nedlægges ved hjælp af din counted pointer.
Hvis du ligeledes sikrer at din counted pointer ikke kan oprettes på heapen
(men kun på stakken, globalt eller som datamedlem i en klasse), ved at
overskrive new for din counted pointer, så har man efterhånden gjort det
nemt for brugeren og givet ham et vink med en vognstang.
Ved at tilrettelægge koden passende, kommer du uden om spørgsmålet omkring
"delete this", samtidig med at du fjerner behover for anvendelse af
virtuelle metoder (incl. destructor), med deraf forbedrede performance (tid,
plads og forbedret mulighed for inline funktioner). Faktisk bliver der, hvis
man har en bare nogenlunde compiler, absolut ikke noget overhead (plads,
funktions kald) når nu du har sagt at objektet skal være reference-counted.
Dette er illustreret i nedenstående program:
#include <new>
template <class T>
class cnt_ptr
{
public:
cnt_ptr(T& t_arg) :
t(&t_arg) { t->ref(); }
cnt_ptr(const cnt_ptr<T>& cnt_ptr_arg) :
t(cnt_ptr_arg.t) { t->ref(); }
~cnt_ptr()
{ if(0 == t->unref()) delete t; }
cnt_ptr& operator=(const cnt_ptr<T>& rhs)
{
t->ref();
if(0 == t->unref()) delete t;
t = rhs.t;
return *this;
}
T* operator->()
{ return t; }
const T* operator->() const
{ return t; }
private:
// do not allow cnt_ptr<T> to be dynamic allocated
// make operator new private and not implemented
void* operator new(std::size_t) throw(std::bad_alloc);
void* operator new(std::size_t, const std::nothrow_t&) throw();
void* operator new[](std::size_t) throw(std::bad_alloc);
void* operator new[](std::size_t, const std::nothrow_t&) throw();
void* operator new (std::size_t, void*) throw();
void* operator new[](std::size_t, void*) throw();
private:
T* t;
};
class ref_count_base
{
protected:
ref_count_base() :
count(0) {}
~ref_count_base() {}
unsigned ref(void)
{ return ++count; }
unsigned unref(void)
{ return --count; }
private:
unsigned count;
private:
// copy constructor and copy assignment not allowed
// make private and not implemented
ref_count_base(const ref_count_base&);
ref_count_base& operator=(const ref_count_base&);
};
class my_class : private ref_count_base
{
public:
my_class(int x_arg) :
x_(x_arg) {}
int x(void) const
{ return x_; }
void fail(void)
{ throw 1; }
protected:
friend cnt_ptr<my_class>;
~my_class() {} // Do now allow allocation on stack and global memory
private:
int x_;
private:
my_class(const my_class&);
my_class& operator=(const my_class&);
};
// my_class global_o1(1); // Error: destructor not accessible
int main()
{
{
cnt_ptr<my_class> p1(*new my_class(1));
p1->x();
try {
cnt_ptr<my_class> p2=p1;
p2->x();
p1 = p2;
p1->fail(); // no problem
}
catch(...) {
}
// my_class o1(1); // Error: destructor not accessible
}
return 0;
}
Venlig hilsen
Mogens Hansen
| |
Soeren Sandmann (13-03-2001)
| Kommentar Fra : Soeren Sandmann |
Dato : 13-03-01 16:21 |
|
"Mogens Hansen" <mogens_h@dk-online.dk> writes:
Tak for svaret. Det var meget brugbart.
> Hvis du samtidig gør destructoren til din klasse protected, og klassen er
> friend med din counted pointer, sikrer du at dit objekt kun kan allokeres på
> heapen og kun kan nedlægges ved hjælp af din counted pointer.
Ville der være noget forgjort i at tillade allokering på stakken, hvis
man sørger for ikke at have counted pointers til det?
> cnt_ptr(T& t_arg) :
> t(&t_arg) { t->ref(); }
> cnt_ptr(const cnt_ptr<T>& cnt_ptr_arg) :
> t(cnt_ptr_arg.t) { t->ref(); }
Er der nogen grund til ikke at have constructoren
cnt_ptr (T* t_arg) : t (t_arg)
{
if (t)
t->ref ();
}
og så sørge for at pointerens invariant er "hvis T* ikke er 0, så har
jeg en reference til den"? På den måde kan en counted pointer også være 0.
> cnt_ptr& operator=(const cnt_ptr<T>& rhs)
> {
> t->ref();
> if(0 == t->unref()) delete t;
> t = rhs.t;
> return *this;
> }
Dette forstår jeg ikke. Hvordan kan t->unref() blive 0, når linjen
ovenfor lige har kaldt t->ref()? Skal det ikke være
cnt_ptr& operator=(const cnt_ptr<T>& rhs)
{
if(0 == t->unref()) delete t;
t = rhs.t;
t->ref();
return *this;
}
i stedet, så pointeren unref()er det gamle objekt og ref()er det nye?
| |
Mogens Hansen (14-03-2001)
| Kommentar Fra : Mogens Hansen |
Dato : 14-03-01 00:51 |
|
Hej Søren,
"Soeren Sandmann" <sandmann@daimi.au.dk> wrote in message
news:ye8n1apsniv.fsf@moonstar.daimi.au.dk...
> "Mogens Hansen" <mogens_h@dk-online.dk> writes:
>
>
> Ville der være noget forgjort i at tillade allokering på stakken, hvis
> man sørger for ikke at have counted pointers til det?
Nej, bare man sørger for ikke at have en counted pointer til det - som du
også siger.
Det var også for at illustrere hvordan man kan forhindre stak-allokering,
hvis man ikke vil tillade det.
>
> > cnt_ptr(T& t_arg) :
> > t(&t_arg) { t->ref(); }
> > cnt_ptr(const cnt_ptr<T>& cnt_ptr_arg) :
> > t(cnt_ptr_arg.t) { t->ref(); }
>
> Er der nogen grund til ikke at have constructoren
>
> cnt_ptr (T* t_arg) : t (t_arg)
> {
> if (t)
> t->ref ();
> }
>
> og så sørge for at pointerens invariant er "hvis T* ikke er 0, så har
> jeg en reference til den"? På den måde kan en counted pointer også være
0.
Ikke andet end at jeg (lidt strengt) ikke ville tillade at have en 0 peger -
så slipper man for at teste rundt omkring (i copy-constructor,
copy-assignment og måske i brugen).
Det afhænger at om du har brug for at den kan være 0 - eller måske for at
cnt_ptr kan have en default constructor.
>
> > cnt_ptr& operator=(const cnt_ptr<T>& rhs)
> > {
> > t->ref();
> > if(0 == t->unref()) delete t;
> > t = rhs.t;
> > return *this;
> > }
>
> Dette forstår jeg ikke. Hvordan kan t->unref() blive 0, når linjen
> ovenfor lige har kaldt t->ref()? Skal det ikke være
>
> cnt_ptr& operator=(const cnt_ptr<T>& rhs)
> {
> if(0 == t->unref()) delete t;
> t = rhs.t;
> t->ref();
> return *this;
> }
>
> i stedet, så pointeren unref()er det gamle objekt og ref()er det nye?
Undskyld, det var en fejl.
Jeg startede med at lave den som du foreslår, men den tager ikke hensyn til
self-assignment (this == &rhs).
Derfor flyttede jeg "t->ref();" op i toppen - det skulle naturligvis have
været "rhs.t->ref();"
Man kan naturligvis også teste for self-assignment.
Men da self-assignment (formodentligt) er et sjældent tilfælde, påtrykker
man alle de almindelige tilfælde (ikke self-assignment) et performance
overhead (optimize for the common case).
Venlig hilsen
Mogens Hansen
PS. Hvis jeg har skrevet noget vrøvl, så kan det være fordi jeg er træt -
bare spørg igen.
| |
michael Nielsen (12-03-2001)
| Kommentar Fra : michael Nielsen |
Dato : 12-03-01 20:45 |
|
Du skal naturligvis sikre dig der er noget at delete, dvs objektet
skal være allokeret på heapen ikke på stakken.
Derudover skal delete this være det sidste der foregår i objektet.
Michael.
"Soeren Sandmann" <sandmann@daimi.au.dk> wrote in message
news:ye8y9ucvuqg.fsf@rangerover.daimi.au.dk...
> Er nedenstående fornuftigt hvis man har sat sig i hovedet at man vil
> have refcounting på sine objekter? Jeg tænker specielt på om
> 'delete this' er sundt.
>
> Man skal naturligvis være forsigtig med objekter der er allokeret på
> stakken. Det er jeg klar over.
>
> class C {
> int ref_count;
> public:
> C (void) { ref_count = 1; }
> virtual void ref () {
> ref_count++;
> };
> virtual void unref () {
> if (--ref_count == 0)
> delete this;
> }
> virtual ~C (void) {}
> };
| |
Ivan Johansen (12-03-2001)
| Kommentar Fra : Ivan Johansen |
Dato : 12-03-01 20:55 |
|
Soeren Sandmann wrote:
>
> Er nedenstående fornuftigt hvis man har sat sig i hovedet at man vil
> have refcounting på sine objekter? Jeg tænker specielt på om
> 'delete this' er sundt.
>
> Man skal naturligvis være forsigtig med objekter der er allokeret på
> stakken. Det er jeg klar over.
>
> class C {
> int ref_count;
> public:
> C (void) { ref_count = 1; }
> virtual void ref () {
> ref_count++;
> };
> virtual void unref () {
> if (--ref_count == 0)
> delete this;
> }
> virtual ~C (void) {}
> };
Metoden er formodentlig brugbar. Jeg foretrækker dog at bruge en static
variabel:
class C
{
static unsigned ref_count;
public:
C(){ref_count++;};
~C(){ref_count--;};
};
unsigned C::ref_count = 0;
Jeg håber det hjælper dig.
Ivan Johansen
| |
Mogens Hansen (12-03-2001)
| Kommentar Fra : Mogens Hansen |
Dato : 12-03-01 21:25 |
|
Hej Ivan,
"Ivan Johansen" <graph@padowan.dk> wrote in message
news:3AAD29AA.BE91D827@Hotmail.com...
>
> Metoden er formodentlig brugbar. Jeg foretrækker dog at bruge en static
> variabel:
> class C
> {
> static unsigned ref_count;
> public:
> C(){ref_count++;};
> ~C(){ref_count--;};
> };
> unsigned C::ref_count = 0;
>
> Jeg håber det hjælper dig.
Du tæller hvor mange instancer af typen C der findes - ikke hvor mange
referencer der er til en given instans af typen C.
Det er 2 helt forkellige ting. Det du angiver, er ikke det man normalt
opfatter som reference counting (se f.eks. "Advanced C++, Programming Styles
and Idiom", James O. Coplien).
Venlig hilsen
Mogens Hansen
| |
Ivan Johansen (12-03-2001)
| Kommentar Fra : Ivan Johansen |
Dato : 12-03-01 21:57 |
|
Mogens Hansen wrote:
> Du tæller hvor mange instancer af typen C der findes - ikke hvor mange
> referencer der er til en given instans af typen C.
> Det er 2 helt forkellige ting. Det du angiver, er ikke det man normalt
> opfatter som reference counting (se f.eks. "Advanced C++, Programming Styles
> and Idiom", James O. Coplien).
Undskyld. Jeg misforstod spørgsmålet.
Ivan Johansen
| |
Mogens Hansen (12-03-2001)
| Kommentar Fra : Mogens Hansen |
Dato : 12-03-01 22:30 |
|
Hej Søren,
"Soeren Sandmann" <sandmann@daimi.au.dk> wrote in message
news:ye8y9ucvuqg.fsf@rangerover.daimi.au.dk...
> Er nedenstående fornuftigt hvis man har sat sig i hovedet at man vil
> have refcounting på sine objekter?
Se
http://www.boost.org/libs/smart_ptr/shared_ptr.htm
Venlig hilsen
Mogens Hansen
| |
|
|