Black Friday = Pricewatch Bekijk onze selectie van de beste Black Friday-deals en voorkom een miskoop.

[C++] Generieke pointers naar template types

Pagina: 1
Acties:

  • Darkvater
  • Registratie: Januari 2001
  • Laatst online: 26-08-2024

Darkvater

oh really?

Topicstarter
Stel ik heb een array van pointers en die wil ik allemaal naar specifieke templates specialisations laten wijzen die allemaal van verschillende typen kunnen zijn. Eg zoiets als maar dan met templates:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class myClass {
  int a;
};

int main() {
  myClass a;
  myClass b;
  myClass c;

  myClass* base[3];

  base[0] = &a;
  base[1] = &b;
  base[2] = &c;
  [s][/s]return 0;
}


C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class T> class myClass {
  T a;
};

int main() {
  myClass<int> a;
  myClass<char> b;
  myClass<double> c;

  myClass* base[3];

  base[0] = &a;
  base[1] = &b;
  base[2] = &c;


De template versie compile-t natuurlijk niet, maar hoe krijg ik het voor elkaar dat het wel juist werkt? Die type informatie ben ik natuurlijk kwijt als ik de classes zo opsla op dit moment. Om het werkend te krijgen heb ik een paar heel lelijke hacks (eg casts) gebruikt om het werkend te krijgen (zie onderin) waar ik dan de type steeds had wanneer ik deze classes gebruikte en naar het juiste type casttte. Alleen de destructor ging verkeerd omdat het van myClass<void*> werd aangeroepen en niet van de juiste implementaties. Is wat ik wil wel mogelijk?

C++:
1
2
myClass<void*>* base[3];
base[0] = reinterpret_cast<myClass<void*>*>(&a);

[ Voor 3% gewijzigd door Darkvater op 21-10-2008 22:12 ]


Windows Vista? *NEVER* Het waarom - Opera forever!!!
I've seen chickens that were more menacing. Chickens in a coma. On ice. In my fridge


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 19:52
Waarom geen common base class maken die je template specialiaties subclassen? Die base class moet dan natuurlijk een virtual destructor hebben, als je dynamisch gealloceerde instanties wil vrijgeven via een pointer naar een base class.

Overigens zou ik niet weten waarom dat in je voorbeeld fout zou gaan, want je base-array bevat slechts pointers, dus dat resulteert niet in het uitvoeren van enige destructor code.

  • Darkvater
  • Registratie: Januari 2001
  • Laatst online: 26-08-2024

Darkvater

oh really?

Topicstarter
Sorry, niet helemaal duidelijk geweest. Zo dan. Het deleten van elke pointer waar base naar verwijst zal fout gaan omdat myClass<void*> wordt aangeroepen en niet int/char/double.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template <class T> class myClass {
public:
  myClass() {ptr = new T[10];}
  ~myClass() {delete[] ptr;}
  T a;
private:
  T *ptr;
};

int main() {
  myClass<int> a;
  myClass<char> b;
  myClass<double> c;
  a.a = 1;
  b.a = 2;
  c.a = 3;

  myClass<void*> **base = new myClass<void*>*[3];

  base[0] = reinterpret_cast<myClass<void*>*>(&a);
  base[1] = reinterpret_cast<myClass<void*>*>(&b);
  base[1] = reinterpret_cast<myClass<void*>*>(&c);

  for (int i = 0; i != 3; i++) delete base[i];
  delete[] base;
  return 0;
]

[ Voor 7% gewijzigd door Darkvater op 21-10-2008 22:37 ]


Windows Vista? *NEVER* Het waarom - Opera forever!!!
I've seen chickens that were more menacing. Chickens in a coma. On ice. In my fridge


  • writser
  • Registratie: Mei 2000
  • Laatst online: 13-11 10:55
Dat is toch logisch? Je forceert zelf een cast naar <void*>, dan wordt die destructor ook aangeroepen als je het object verwijdert. Volgens mij haal je wat dingen door elkaar, want casten naar een ander template-type heeft niks met base-klasses te maken.

Als je een baseclass wil gebruik je de oplossing van Soultaker: maak een myIntClass, MyDoubleClass etc. Vervolgens stop je alle pointers naar instanties van deze klasses in een std::vector<myClass*> en dan gaat "alles" vanzelf goed.

Je kan niet zomaar allemaal verschillende template-types naar elkaar casten en/of in een array stoppen. Templates zijn juist type-safe. Wat je nu probeert is hetzelfde als een array maken met ints, doubles en floats er in. Waardoor je van alles moet casten. Voorzover ik weet is dat geen 'good practice'. Maar neem dat niet van mij aan, wacht op een reactie van oisyn ofzo. :)

[ Voor 56% gewijzigd door writser op 21-10-2008 22:57 ]

Onvoorstelbaar!


  • Darkvater
  • Registratie: Januari 2001
  • Laatst online: 26-08-2024

Darkvater

oh really?

Topicstarter
writser schreef op dinsdag 21 oktober 2008 @ 22:43:
Dat is toch logisch? Je forceert zelf een cast naar <void*>, dan wordt die destructor ook aangeroepen als je het object verwijdert. Volgens mij haal je wat dingen door elkaar, want casten naar een ander template-type heeft niks met base-klasses te maken.
Dat is ook juist mijn probleem dat de "verkeerde" destructor wordt aangeroepen. Die base is inderdaad geen base-class ofzo, maar gewoon de basis-pointer naar de classes. Naam misschien verkeerd gekozen.
writser schreef op dinsdag 21 oktober 2008 @ 22:43:Als je een baseclass wil gebruik je de oplossing van Soultaker: maak een myIntClass, MyDoubleClass etc. Vervolgens stop je alle pointers naar instanties van deze klasses in een std::vector<myClass*> en dan gaat "alles" vanzelf goed.
Bedoel je het onderstaande, wat Soultaker ook zei?
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class myClassParent {
public:
  virtual ~myClassParent() {};
};

template <class T> class myClass: public myClassParent {
public:
  myClass() {ptr = new T[10];}
  ~myClass() {
  delete[] ptr;
  ptr = NULL;
  }
  T a;
private:
  T *ptr;
};

int main() {
  myClass<int> *a = new myClass<int>;
  myClass<char> *b = new myClass<char>;
  myClass<double> *c = new myClass<double>;
  a->a = 1;
  b->a = 2;
  c->a = 3;

  myClassParent **base = new myClassParent*[3];
  base[0] = a;
  base[1] = b;
  base[2] = c;

  for (int i = 0; i != 3; i++) delete base[i];
  delete[] base;

  return 0;
}


Maar dit betekent dan dat als ik een functie aan wil roepen van myClass uit de base-pointer (van type myClassParent) ik deze functie virtual moet maken voor myClassParent. Maar hoe werkt dit dan als een parameter/return type templatised is? Als op die ??? niet de juiste parameter staat, roept het de base-class functie aan, en niet die van de derived class. Bijvoorbeeld:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class myClassParent {
public:
  virtual void add(????) {}
};

template <class T> class myClass: public myClassParent {
public:
  void add(T val) {/*dosomething*/}
};

...
myClass<int> *a = new myClass<int>;
myClassParent **base = new myClassParent*[3];
base[0]->add(12);


Windows Vista? *NEVER* Het waarom - Opera forever!!!
I've seen chickens that were more menacing. Chickens in a coma. On ice. In my fridge


  • writser
  • Registratie: Mei 2000
  • Laatst online: 13-11 10:55
Ik snap wat je wil bereiken, maar volgens mij probeer je een verkeerd probleem op te lossen hier .. :P Je kan toch niet zomaar ergens 12 bij optellen? Wat als in je array een instantiatie van myClass<std::string> zit? Dan gaat er iets goed verkeerd :P. Het idee van inheritance is juist dat je alle gedeelde functionaliteit in de base class hebt zitten. Bijvoorbeeld een virtuele functie myClass.print(). Die maak je dan in elke subklasse opnieuw aan en laat je de goede informatie uitprinten.

Wil je functionaliteit gebruiken die alleen in een bepaalde subklasse beschikbaar is dan kun je met dynamic_cast<> veilig downcasten. Voor het probleem wat je in de bovenstaande post wil oplossen heb je helemaal geen template classes nodig. Even wat code uit mijn mouw geschud):

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class myClass {
public:
  virtual void print();
};

class myDoubleClass: public myClass {
public:
  myDoubleClass(double value) {d = value;}
  void print() { std::cout << "double: " << d; }
  double d;
};

myClass * mc = new myDoubleClass(3.0);
std::vector<myClass *> v;
v.push_back(mc);

// print "double: 3.0"
v[0]->print();

[ Voor 28% gewijzigd door writser op 21-10-2008 23:33 ]

Onvoorstelbaar!


  • Darkvater
  • Registratie: Januari 2001
  • Laatst online: 26-08-2024

Darkvater

oh really?

Topicstarter
Dank je writser... ik ben inderdaad met de verkeerde mindset ingegaan. Dat forceful casten van die pointers was al verkeerd. Met base en derived class + virtual (destructor) werkt het allemaal wel.
Wat ik nodig had was dynamic_cast<>, ben ik niet eens opgekomen... stom.

Dankzij jullie ziet de implementatie er ook veel mooier uit. Ik heb alle common functionaliteit in de base class gezet, met een virtual destructor en virtuele functies voor wat het moest en de echt-specifieke functies via een dynamic cast uitgevoerd. Mijn dank is groot :)


Windows Vista? *NEVER* Het waarom - Opera forever!!!
I've seen chickens that were more menacing. Chickens in a coma. On ice. In my fridge


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Een ontwerp dat dynamic_cast nodig heeft is bijna altijd fout (zijn een paar uitzonderingen). En veilig... als je met null-pointers om kan gaan overal in je code.

Je kan ook zoiets doen:

C++:
1
2
3
4
5
6
7
8
9
10
11
template <typename T>
class Number {
   Number(T& x) : n_(x) {}
   ~Number() {}

   template <typename U> void add(Number<U>& num) {
      n_ += num.n_;
   }

   T n_;
};


Voor return types moet je dan traits gebruiken, of result_of<>::type

[ Voor 48% gewijzigd door Zoijar op 22-10-2008 10:13 ]


  • writser
  • Registratie: Mei 2000
  • Laatst online: 13-11 10:55
Maar zo kun je Number<float>, Number<std::string> etc. toch niet in een vector gooien?
offtopic:
disclaimer: ik ben geen C++ guru.

[ Voor 22% gewijzigd door writser op 22-10-2008 10:18 ]

Onvoorstelbaar!


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Nee, klopt. Maar waarom wil je dat? Vaak is het wel anders op te lossen. Met die base class gooi je alle info die je hebt weg en wordt alles runtime opnieuw bepaald. Plus dat het niet extendable is, want dan moet je overal die casts gaan aanpassen met extra cases. Soms kan het echt niet anders, maar ideaal is het niet.

  • writser
  • Registratie: Mei 2000
  • Laatst online: 13-11 10:55
Ik wil dat niet, maar de topicstarter wel :P. Daarom zei ik ook dat die een verkeerd probleem probeert op te lossen. In welke context heb je dit nodig?

Onvoorstelbaar!


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 14-11 23:57

.oisyn

Moderator Devschuur®

Demotivational Speaker

De fout die je maakt is dat je denkt dat verschillende template instantiations aan elkaar gerelateerd zijn. Dat is niet zo. Een myClass<int> heeft net zoveel te maken met een myClass<float> als een std::string met een std::fstream. Helemaal niets dus.

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.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Ze kunnen hetzelfde concept implementeren, dan zijn ze wel gerelateerd in principe.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 14-11 23:57

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ja, maak het voor de TS even ingewikkelder dan het is. Uiteraard had ik het over de type-relatie voor de compiler :)

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: 13-09 00:05
Het probleem van de TS is uiteindelijk dat hij niet helemaal beseft wat de consequenties van een heterogene collectie zijn. Templates zijn een C++ iets, die lossen niet zomaar zijn fundamentele OO probleem op.

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


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

.oisyn schreef op woensdag 22 oktober 2008 @ 11:54:
Ja, maak het voor de TS even ingewikkelder dan het is. Uiteraard had ik het over de type-relatie voor de compiler :)
O-) Daar ben ik altijd goed in... :P
Pagina: 1