[C++] reference counting en this pointer

Pagina: 1
Acties:
  • 114 views sinds 30-01-2008
  • Reageer

  • MisterData
  • Registratie: September 2001
  • Laatst online: 21-01 13:45
Voor een programma heb ik een tijd geleden een mooi systeem gemaakt voor reference counting. Het werkt als volgt: een object maak ik met een aanroep als GC::Hold(new EenOfAndereKlasse()); en die geeft me een ref<EenOfAndereKlasse> terug. GC::Hold is als volgt geimplementeerd:

C++:
1
2
3
4
template<class T> ref<T> GC::Hold(T* x) {
    Resource<T>* rs = new Resource<T>(x);
    return rs->Reference();
}


Resource<T> is dus de class met de reference count en houdt een pointer vast naar het echte object, in deze code de pointer x.

Nu is het probleem eigenlijk dat de gemaakte classes geen refcounted this-pointer hebben. Nu dacht ik dat op te lossen door de classes die dat nodig hadden te extenden van een class Object die een pointer naar de Resource als member zou hebben. In GC::Hold wordt die pointer dan gevuld:

C++:
1
2
3
4
5
6
7
8
class EenOfAndereClass: public virtual Object {

};

class Object {
   Resource<Object>* object;
   ref<Object> This();
};


(De ref<Object> is te casten naar een ref<EenOfAndereClass>. Ik heb nog bedacht om ook Object templated te maken, Object<EenOfAndereClass>, maar dat werkt dan natuurlijk weer niet als EenOfAndereClass weer gesubclassed wordt...).

Het probleem zit 'em erin dat ik in GC::Hold niet kan zien of een object subclass is van Object. Normaal doe je dat met een dynamic_cast<Object>(x) (en als dat 0 oplevert is het geen Object), maar een dynamic_cast mag niet bij non-polymorphic types als bijvoorbeeld std::vector, die ik redelijk vaak via GC::Hold aanmaak.

Hoe kan ik dat oplossen? Overloading lijkt niet te werken: als ik een template<> ref<Object> GC::Hold(Object* obj) maak dan wordt die niet aangeroepen als ik GC::Hold(new EenOfAndereClass) doe, waarbij EenOfAndereClass dus subclass van Object is....

Voor de duidelijkheid dus wat ik eigenlijk wil (maar dan ook op non-polymorphic types...)

C++:
1
2
3
4
5
6
7
8
9
10
template<class T> ref<T> GC::Hold(T* x) {
  Resource<T>* rs = new Resource<T>(x);

  Object* object = dynamic_cast<Object*>(x);
  if(object!=0) {
    object->_resource = rs;
  }

  return rs->Reference();
}

[ Voor 18% gewijzigd door MisterData op 07-07-2006 13:08 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 27-01 15:20

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik snap het niet helemaal. Waarom store je de refcount niet gewoon in Resource<T> (waar gebruik je Resource anders voor?).

En een andere vraag: waarom via de omweg van een GC::Hold, je kunt toch net zo goed zelf een ref<T> op de stack maken (die in zijn constructor weer een Resource<T> alloceert)?

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MisterData
  • Registratie: September 2001
  • Laatst online: 21-01 13:45
.oisyn schreef op vrijdag 07 juli 2006 @ 15:12:
Ik snap het niet helemaal. Waarom store je de refcount niet gewoon in Resource<T> (waar gebruik je Resource anders voor?).

En een andere vraag: waarom via de omweg van een GC::Hold, je kunt toch net zo goed zelf een ref<T> op de stack maken (die in zijn constructor weer een Resource<T> alloceert)?
De resource houdt ook de reference count bij, het gaat er meer om dat de instantie van een class, als ie dat wil, een reference kan aanmaken die naar zichzelf verwijst (soort this-pointer dus), maar dat kan dus nu niet, aangezien die class geen pointer naar z'n eigen Resource<T> heeft (en de ref<T> wil een pointer naar de Resource<T> om daar de reference count op te hogen). Ik probeer die pointer in een speciale superclass te proppen zodat áls een class dat wil hij die pointer kan krijgen.

En de GC::Hold functie is gemaakt zodat ik eventueel kan loggen vanaf waar een bepaald object wordt aangemaakt :) De twee regels van die functie die hier staan zijn een versimpelde versie van wat in m'n echte code staat :)

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 27-01 15:20

.oisyn

Moderator Devschuur®

Demotivational Speaker

MisterData schreef op vrijdag 07 juli 2006 @ 15:30:

De resource houdt ook de reference count bij, het gaat er meer om dat de instantie van een class, als ie dat wil, een reference kan aanmaken die naar zichzelf verwijst (soort this-pointer dus), maar dat kan dus nu niet, aangezien die class geen pointer naar z'n eigen Resource<T> heeft (en de ref<T> wil een pointer naar de Resource<T> om daar de reference count op te hogen).
Aha, nu snap ik je probleem. Ik maak even de reactie af voor ik terugga naar de topicstart :)
En de GC::Hold functie is gemaakt zodat ik eventueel kan loggen vanaf waar een bepaald object wordt aangemaakt :) De twee regels van die functie die hier staan zijn een versimpelde versie van wat in m'n echte code staat :)
Maar GC::Hold kun je net zo goed vanuit de ctor van ref<T> aanroepen ;)

Terug naar de topicstart:
Het probleem zit 'em erin dat ik in GC::Hold niet kan zien of een object subclass is van Object. Normaal doe je dat met een dynamic_cast<Object>(x) (en als dat 0 oplevert is het geen Object), maar een dynamic_cast mag niet bij non-polymorphic types als bijvoorbeeld std::vector, die ik redelijk vaak via GC::Hold aanmaak
Het zou natuurlijk ook van de zotte zijn om een run-time check te doen voor iets wat je at compile-time al kan weten ;). Gelukkig zijn er trucjes voor, mbv function overloads en de sizeof operator (die het mogelijk maakt expressies te 'evalueren' zonder ze uit te voeren).

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<class DERIVED, class BASE> struct IsSubclassOf
{
private:
    typedef char small_type;
    struct large_type { char x[10]; };

    static small_type func(BASE*);
    static large_type func(...);

public:
    static const bool value = (sizeof(func((DERIVED*)0)) == sizeof(small_type));
};

struct A { };
struct B : public A { };
struct C { };

int main()
{
    std::cout << "B is subclass of A: " << IsSubclassOf<B, A>::value << std::endl;
    std::cout << "C is subclass of A: " << IsSubclassOf<C, A>::value << std::endl;
}


Vervolgens kun je IsSubclassOf<T, Object>::value dus gebruiken in je GC::Hold funcie, dmv template specialisatie (de if gaat niet werken, dan krijg je compile errors als je 'm gebruikt met een object dat geen subclass is van Object).


.edit: ik bedenk me nu trouwens dat je die hele IsSubclassOf natuurlijk ook meteen kunt overslaan :)
C++:
1
2
3
4
5
6
7
8
9
10
11
template<class T> static void SetResource(Object * o, Resource<T> * rs) { o->_resource = rs; }
static void SetResource(...) { }

template<class T> ref<T> GC::Hold(T* x)
{ 
  Resource<T>* rs = new Resource<T>(x); 

  SetResource(x, rs);

  return rs->Reference(); 
}


Ik neem trouwens aan dat je een algemene baseclass hebt voor alle Resource<T>'s? Anders kun je die natuurlijk nooit opslaan in een Object.

[ Voor 37% gewijzigd door .oisyn op 07-07-2006 16:19 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 10-12-2025
MisterData schreef op vrijdag 07 juli 2006 @ 13:05:
Het probleem zit 'em erin dat ik in GC::Hold niet kan zien of een object subclass is van Object. Normaal doe je dat met een dynamic_cast<Object>(x) (en als dat 0 oplevert is het geen Object), maar een dynamic_cast mag niet bij non-polymorphic types als bijvoorbeeld std::vector, die ik redelijk vaak via GC::Hold aanmaak.

Hoe kan ik dat oplossen?
C++:
1
2
3
4
struct Big { char a[2]; }
char IsObjectHelper(Object*);
Big IsObjectHelper(...);
bool result = sizeof(IsObjectHelper(x))==1;

sizeof is compile-time, dus IsObjectHelper() wordt niet eens aangeroepen, en een definitie is dus ook niet nodig.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 00:56
Als je op dit soort rare hacks moet teruggrijpen dan zit er echt iets mis in je ontwerp.

Mijn idee is dat als het argument van Hold() allerlei verschillende objecten kan zijn, het niet logisch is om die toekenning aan een member van Object in Hold() te willen doen. Je kunt het er veel beter buiten halen, naar mijn idee, en gewoon het resultaat van Hold() door de caller laten toekennen.

Verder kun je geen geschikte _resource member declareren in Object, omdat het type daarvan afhangt van de specifieke klasse die vastgehouden wordt. Je kunt een ref<Derived>* immers niet zomaar toekennen aan een ref<Object>*.

Tenslotte kan ik me niet echt indenken waarom je die pointer terug nu precies wil hebben. Het idee van de ref<T> classes is juist dat je ze mee kunt geven in plaats van het originele object (neem ik aan) en het echte object gebruik je alleen voor functies die helemaal niets met reference counting (kunnen) doen.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 27-01 15:20

.oisyn

Moderator Devschuur®

Demotivational Speaker

Soultaker schreef op vrijdag 07 juli 2006 @ 21:31:
Tenslotte kan ik me niet echt indenken waarom je die pointer terug nu precies wil hebben. Het idee van de ref<T> classes is juist dat je ze mee kunt geven in plaats van het originele object (neem ik aan) en het echte object gebruik je alleen voor functies die helemaal niets met reference counting (kunnen) doen.
Strict gezien hoort een refcount natuurlijk niet in een object, ondermeer omdat dat vereist dat een class die je wilt refcounten een bepaalde base wilt hebben, waar je lang niet altijd voor kunt zorgen. Het probleem is echter dat je vanuit de code van het object zelf (waar je dus de rauwe this pointer hebt) geen ref<T> kunt maken, omdat je dan verschillende refcounts krijgt voor hetzelfde object.

Ik vind het niet zo vreemd om voor de objecten die dat willen (en dus overerven van Object) iets meer functionaliteit toe te voegen, maar dat verder transparant te doen tov de rest van de objecten die je wilt refcounten. De maker van de smartpointer zou hier allerminst verantwoordelijk voor moeten zijn, dat is vragen om problemen. Als je bijvoorbeeld een object ooit uitbreidt door 'm ook te laten overerven van Object zul je op elke plek waar dat ding aangemaakt wordt de code moeten aanpassen, dat lijkt me allerminst een goede werkbare situatie

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MisterData
  • Registratie: September 2001
  • Laatst online: 21-01 13:45
Soultaker schreef op vrijdag 07 juli 2006 @ 21:31:
Als je op dit soort rare hacks moet teruggrijpen dan zit er echt iets mis in je ontwerp.
Het lijkt misschien een rare hack, maar daarmee kan ik wel iedere denkbare klasse (ook uit libraries zoals de STL waar ik niets in kan veranderen) met dezelfde ref-class gebruiken :) In mijn ogen rechtvaardigd dat het wel. Daarnaast is de 'hack' volledig volgens de standaarden te implementeren...
Mijn idee is dat als het argument van Hold() allerlei verschillende objecten kan zijn, het niet logisch is om die toekenning aan een member van Object in Hold() te willen doen. Je kunt het er veel beter buiten halen, naar mijn idee, en gewoon het resultaat van Hold() door de caller laten toekennen.
Ben ik met je eens, bij templated functies maak je voor de uitzonderingen een specialisatie van de functie. Helaas werkt dat hier niet, omdat als je een template<> GC::Hold(Object*) maakt die niet verkozen zal worden boven de algemene template als je als parameter een derived class van Object meegeeft.
Verder kun je geen geschikte _resource member declareren in Object, omdat het type daarvan afhangt van de specifieke klasse die vastgehouden wordt. Je kunt een ref<Derived>* immers niet zomaar toekennen aan een ref<Object>*.
Klopt, maar dat is eigenlijk niet zo erg. Zoals ik al zei, voor 1 niveau is het nog te doen om Object zelf templated te maken: Object<Derived>. Maar voor meerdere niveaus gaat dat niet. Maar dynamic_cast gaat vanzelf bij de ref-class die ik heb gemaakt, en de betreffende downcast werkt dus altijd :)
Tenslotte kan ik me niet echt indenken waarom je die pointer terug nu precies wil hebben. Het idee van de ref<T> classes is juist dat je ze mee kunt geven in plaats van het originele object (neem ik aan) en het echte object gebruik je alleen voor functies die helemaal niets met reference counting (kunnen) doen.
Nee, een reference vervangt een pointer, niet het object zelf. Ik heb gekozen voor non-intrusive refcounting (dus geen refcount in het object zelf) omdat het dan ook werkt met classes uit andere libraries die ik niet kan aanpassen.
Pagina: 1