Toon posts:

[c++] Class derived from template class.

Pagina: 1
Acties:

Verwijderd

Topicstarter
Wederom een vraagje over templates, ik ben er nog niets zo'n ongeloofelijke held in.

Stel heb een abstracte base class 'collection' en twee derived classes 'list' en 'tree'. Misschien zelfs enkele andere implementaties met elk zijn voor en nadelen. De base class is een generieke interface naar enkele concrete implementaties van een lijstachtige data structuur. Elke collection implementatie ondersteunt methods zoals insert, delete, select, etc.

Code snapshot van base class:
C++:
1
2
3
4
5
6
7
8
template <class Data, class Key>
class collection
{
  ...
  virtual void insert (Data *data, Key *key) = 0;
  virtual Data *select (Key key) = 0;
  ...
};


Code snapshot van list en tree class:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <class Data, class Key>
class list : collection
{
  ...
  void insert (Data *data, Key *key)
  {
    /* insert into internal linked list */
  }
  ...
};

template <class Data, class Key>
class tree : collection
{
  ...
  Data *select (Key key)
  {
    /* select item from internal binary tree */
  }
  ...
};
Moge dat duidelijk zijn.

Nu heb ik ergens in mijn programma een collectie van bijvoorbeeld modules nodig. Voordat een modules in een lijst toegevoegd mogen worden, moeten er een aantal checks gedaan worden. Dit zou ik willen doen door een derived class module_collection te maken en de insert method er ongeveer zo uit te laten zien:
C++:
1
2
3
4
5
6
7
8
  void insert (Data *data, Key *key)
  {
    if (data == OK) {
      log("new module added");
      super::insert(data, key)
    } else
      throw nare_exception();
  }

Maar... nu komt het. Ik wil deze module_collection niet laten deriven van een list of een tree of een andere gespecificeerde implementatie. Ik wil deze module_collection een derived class laten zijn van een willekeurige collection. Ik weet echter niet of en hoe dit kan.

Ik dacht zelf aan zoiets, maar dit schijnt niet te mogen:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
template <class Data, class Key, class Base>
class module_collection : Base<Data, Key>
{
  ...
};

/* als ik dan een module_collection als list wil: */
collection<module, mod_name> *mods =
    new module_collection<module, mod_name, list>;

/* of als tree */
collection<module, mod_name> *mods =
    new module_collection<module, mod_name, tree>;

Iemand enige idee of deze vorm van polymorphisme ongeveer op deze manier te implementeren is in C++, of dat ik dit op een hele andere 'mooie' manier kan oplossen?

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Voor het technische gedeelte: je kan een template template parameter gebruiken:

C++:
1
2
3
4
5
6
7
8
9
template <typename Data, typename Key> class Base {};
template <typename Data, typename Key> class Collection : public Base<Data, Key> {};

template <typename Data, typename Key, template <typename, typename> class C>
class module_collection : public C<Data, Key> {

};

module_collection<module, mod_name, Collection> xxx;

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Een aanrader is trouwens wel C++ Templates - The Complete Guide

  • MisterData
  • Registratie: September 2001
  • Laatst online: 09-04 12:07
Kunt ook met een policy werken :) Google maar eens op policies, policy templates of traits. Het komt erop neer dat je een aparte template parameter maakt die naar een class wijst die de insert-methode implementeert. Het lijkt een beetje op het 'traits'-systeem in STL: je hebt een class basic_string en die heeft als template parameter een 'traits'-class die aangeeft hoe er met dat type characters gewerkt moet worden. De definitie van string en wstring zien er dan ook ongeveer zo uit:

C++:
1
2
typedef basic_string<char_traits> string;
typedef basic_string<wchar_traits> wstring; // let op de w!


:) Met policies kun je dus eigenlijk steeds een class 'samenstellen' op basis van bepaalde onderdelen en gedrag. Voor dit specifieke geval zou je een insert_policy kunnen maken met de methode insert erin.

Edit: een wat concreter voorbeeld :)

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<typename T> class default_insert_policy {
  public:
    void insert(const T& x) {
    }
};

template<typename T> class test_insert_policy {
  public:
    void insert(const T& x) {
      //hier je checks voordat je insert, eventueel exception gooien
    }
};

// let op de spatie tussen "> >" hieronder!
template<typename T, class InsertionPolicy=default_insert_policy<T> > class lijst: public InsertionPolicy {
 //bla bla
  public:
    void insert(const T& x) {
      InsertionPolicy::insert(x);
     // hier echt inserten
    }
};


:)

[ Voor 30% gewijzigd door MisterData op 24-09-2005 20:48 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

MisterData schreef op zaterdag 24 september 2005 @ 20:39:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<typename T> class default_insert_policy {
  public:
    void insert(const T& x) {
    }
};

template<typename T> class test_insert_policy {
  public:
    void insert(const T& x) {
      //hier je checks voordat je insert, eventueel exception gooien
    }
};

// let op de spatie tussen "> >" hieronder!
template<typename T, class InsertionPolicy=default_insert_policy<T> > class lijst: public InsertionPolicy {
 //bla bla
  public:
    void insert(const T& x) {
      InsertionPolicy::insert(x);
     // hier echt inserten
    }
};
Je kan de policy beter zo schrijven:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename T> default_insert_policy;
template <typename T, template <typename> class IP = default_insertion_policy>
class Lijst : public IP<T> {
   typedef IP<T> InsertionPolicy;
};

// now it's:
Lijst<float, MyInserter> list;
// instead of:
Lijst<float, MyInserter<float> > list;

// what does this do ??
Lijst<float, MyInserter<void*> > list;

(plus dat je een protected non-virtual dtor moet hebben in je policy. Je wilt hem nl. niet virtual maken vanwege performance, maar je mag ook geen class kunnen wissen via een cast naar z'n policy met non-virtual dtor, want dat zou undefined behaviour veroorzaken... leuke dingen policies, zo lekker simpel ook hehe. Maar wel erg nuttig soms)

Overigens dacht ik ook even aan policies, maar zijn ze hier imo niet echt van toepassing. Je zou een 'data validation policy' kunnen maken zoals je boven post... maar data validaten is toch geen taak van een collection? Eigenlijk vind ik het sowieso hier niet in passen, ook niet met inheritance. Ik zou eerder voor een aparte functie gaan. Hoe vaak wil je data op een andere manier validaten voor het inserten? Maak er gewoon een functie bij anders. Je moet niet een functie gaan overloaden om andere functionaliteit toe te voegen. insert() heeft dan ineens geen "enkele wel gedefineerde taak" meer, maar doet twee dingen. Dat is niet echt goed ontwerp. Als je het generiek wilt houden kan je of een empty default virtuele validate functie toevoegen bv. Of je kan een losstaande overloaded functie maken:

C++:
1
2
3
4
5
6
7
8
9
10
11
template <typename Container, typename Data, typename Key>
void validate_and_insert(Container& cont, Data* d, Key* k) {
   cont.insert(); // default is no check
}

template <typename Container>
void validate_and_insert(Container& cont, Module* m, Module_Key* k) {
  if (...data validated...) {
      cont.insert(d, k);
  } else throw std::runtime_error("waaa");
}

Eventueel je type containers nog beperken. Nadeel is misschien dat je nu ook zonder te validaten direct items erin kan stoppen. als dat echt niet mag, dan kan je dus in de insert() een call naar een virtuele functie validate doen, die default naar geen actie. Ik zou in ieder geval nooit insert() overloaden, maar buiten dat zijn er dus verschillende manieren. Een validation policy zou ook kunnen, maar ik zou niet te snel naar policies grijpen want er kleven ook nadelen aan. En het maakt je code moeilijker te lezen. Het "template design pattern" springt ook naar voren als je een virtuele validate() toevoegt. In plaats van dynamische inheritance, kan je ook nog static inheritance toepassen etc, je kan het op zo veel manieren doen met templates... wat het best is ligt aan je volledige ontwerp, en je eisen/constraints.

[ Voor 14% gewijzigd door Zoijar op 24-09-2005 21:31 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13:20

.oisyn

Moderator Devschuur®

Demotivational Speaker

Zoijar schreef op zaterdag 24 september 2005 @ 21:28:
Je kan de policy beter zo schrijven:
Vind je? Nu ben je altijd verplicht om een policy te implementeren als een template, misschien wil je een specifieke policy maken voor een enkel type in een unieke situatie. De policies in STL (traits, allocators, etc.) hebben het ook niet.

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...

.oisyn schreef op maandag 26 september 2005 @ 00:20:
Vind je? Nu ben je altijd verplicht om een policy te implementeren als een template, misschien wil je een specifieke policy maken voor een enkel type in een unieke situatie. De policies in STL (traits, allocators, etc.) hebben het ook niet.
Template specialization? :) Als het conceptueel zinnig is om een ander template argument mee te geven aan de policy dan het argument voor de class zelf, dan moet je geen template-template parameter gebruiken maar jouw manier. Als dat niet zinnig is kan je er beter wel een gebruiken imo. Zoals mijn eerdere voorbeeld is MyClass<float, Addition<void*> > alleen maar fout gevoelig, en het is meer type werk. Bij een allocator zou je het ook kunnen doen: std::vector<long, MyAlloc> ipv het nu nodige std::vector<long, MyAlloc<char> > (?) De allocator class blijft hetzelfde: template <typename T> class MyAlloc; Modern C++ Design gebruikt ook vnl. template template parameters voor policies.

[ Voor 3% gewijzigd door Zoijar op 26-09-2005 10:37 ]

Pagina: 1