[C++] Lijst die "alles" kan opslaan

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Rygir
  • Registratie: Mei 2004
  • Laatst online: 01-03 05:45
Bij het maken van een soort generische parameter opslag heb ik gemerkt dat het blijkbaar niet zo simpel is in C++ als ik dacht om te weten te komen welk type gegevens echt worden voorgesteld door een bepaalde pointer.

Ik probeer dus een lijst, maakt even niet uit welk ADT het is dus pak maar even een list, van variabelen waarvan ik bij het invoeren wel weet welk type ze zijn, dus int, double, float, string, MyClass, Foo, Bar, function*, int*, Foo*, wat dan ook. Om ze nu in een list te bewaren in C++ lijkt mij de enige optie void* te zijn? Dus dat je enkel de pointer naar de data opslaat. Er is immers geen basisklasse voor de standaardtypes zoals int.

Maar dan komt het probleem, kan ik nu het type nog achterhalen van die data? In Java zou je dan het type opvragen omdat alles van object overerft en dus kan je heel simpel te weten komen welk object gerepresenteerd wordt en daarnaar casten dynamisch. Maar doordat ik er ook ints in wil kwijt raken, en dat er geen globale "object" class is sowieso in C++ zie ik daar geen mogelijkheid voor.

Enkele dingen die ik heb overwogen :
  • Maak er een pair<iets, void*> van, waarbij die iets ofwel een enum is voor elk type wat ik wens te gebruiken (nadeel : moet telkens die enum uitbreiden, en dat kan al helemaal niet meer als iemand mijn klasse zou herbruiken)
  • ofwel is die iets het datatype... zoals dat je in java de "class" als object kan bewaren, maar in C++ bestaat dat precies niet, je kan niet gewoon als datatype van een parameter "class" of "type" of zo opgeven, of mis ik iets?
  • ofwel via een soort typeof functie, maar daar heb ik geen ervaring mee en ik had de indruk dat die alleen op klasses werkte wederom, en niet op ints of iets dergelijks? En dat is dan misschien ook nog eens compiler afhankelijk of dat ondersteund wordt, wat een nadeel kan zijn, omdat de gegevens over het datatype moeten aangemaakt worden ergens.
Iemand een idee hoe ik een lijst "voor alles" kan maken, en belangrijker nog : de gegevens er ook weer uit halen (op basis van index of een key zoals een naamstring) en kunnen controleren of ze wel het juiste type zijn?

Acties:
  • 0 Henk 'm!

  • DexterDee
  • Registratie: November 2004
  • Laatst online: 12:23

DexterDee

I doubt, therefore I might be

Een simpele array met multi-datatypen is het makkelijkst te maken met de STL* vector/list containers. Wil je een associative array maken, dan zijn de STL* set/map containers een goede keuze.

* Standard Template Library

Klik hier om mij een DM te sturen • 3245 WP op ZW


Acties:
  • 0 Henk 'm!

  • coubertin119
  • Registratie: Augustus 2002
  • Laatst online: 15-09 17:06
Boost.Any lijkt me dan iets voor jou. Zoals ze zelf zeggen:
Discriminated types that contain values of different types but do not attempt conversion between them, i.e. 5 is held strictly as an int and is not implicitly convertible either to "5" or to 5.0. Their indifference to interpretation but awareness of type effectively makes them safe, generic containers of single values, with no scope for surprises from ambiguous conversions.
De controle over het type en de operaties is volledig aan jou, maar een std::list<boost::any> is denk ik wat je zoekt :).

Skat! Skat! Skat!


Acties:
  • 0 Henk 'm!

  • d4nn7
  • Registratie: Mei 2009
  • Laatst online: 15-07 21:37
Boost.Any lijkt me hier inderdaad een interessante optie.
Voor het controleren van het juiste type is typeid() hier mischien een mogelijkheid?
Wikipedia: typeid

Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

De vraag is een beetje waarom je dit nodig hebt... meestal is er een betere oplossing, eventueel met inheritance.

[ Voor 83% gewijzigd door Zoijar op 12-05-2010 10:19 ]


Acties:
  • 0 Henk 'm!

  • coubertin119
  • Registratie: Augustus 2002
  • Laatst online: 15-09 17:06
d4nn7 schreef op woensdag 12 mei 2010 @ 09:13:
Boost.Any lijkt me hier inderdaad een interessante optie.
Voor het controleren van het juiste type is typeid() hier mischien een mogelijkheid?
Wikipedia: typeid
Hij heeft nood aan een container die zowel ints als classes kan opslaan, dan vervalt de mogelijkheid tot typeid.

Skat! Skat! Skat!


Acties:
  • 0 Henk 'm!

  • NC83
  • Registratie: Juni 2007
  • Laatst online: 21-08 21:44
typeid is sowieso niet echt de beste manier om RTTI te doen aangezien het een heleboel string compares gebruikt om het uiteindelijk type te vinden.

ex-FE Programmer: CMR:DiRT2,DiRT 3, DiRT Showdown, GRID 2, Mad Max


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
NC83 schreef op woensdag 12 mei 2010 @ 11:11:
typeid is sowieso niet echt de beste manier om RTTI te doen aangezien het een heleboel string compares gebruikt om het uiteindelijk type te vinden.
Waar haal je dat idee vandaan? Ik zie daar absoluut geen noodzaak voor. Simpele pointer compares zijn voldoende om type_info::before mee te implenteren.

[ Voor 33% gewijzigd door MSalters op 12-05-2010 11:47 ]

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


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:51

.oisyn

Moderator Devschuur®

Demotivational Speaker

std::vector<boost::any>
.edit: oh zie nu dat coubertin dat ook al aangedragen heeft :)
coubertin119 schreef op woensdag 12 mei 2010 @ 11:00:
[...]
Hij heeft nood aan een container die zowel ints als classes kan opslaan, dan vervalt de mogelijkheid tot typeid.
Nonsens, typeid(int) heeft ook gewoon een type_info instance. Idd, als je typeid() wilt aanroepen op een reference dan is het van belang dat dat type een vtable heeft, wat idd voor iets als int maar ook voor veel classes (zoals std::vector) niet opgaat. Maar je kunt ook de type_info storen zodra je iets in de array zet.

[ Voor 134% gewijzigd door .oisyn op 12-05-2010 11:53 ]

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.


Acties:
  • 0 Henk 'm!

  • NC83
  • Registratie: Juni 2007
  • Laatst online: 21-08 21:44
Ik geloof dat ik dit in Game Gems heb gelezen maar weet het niet meer zeker. Het kan best zijn dat de huidige implementatie dit niet meer doet. Maar idd de correcte implementatie kan gedaan worden aan de hand van een pointer compare.

ex-FE Programmer: CMR:DiRT2,DiRT 3, DiRT Showdown, GRID 2, Mad Max


Acties:
  • 0 Henk 'm!

Verwijderd

Zoijar schreef op woensdag 12 mei 2010 @ 10:18:
De vraag is een beetje waarom je dit nodig hebt... meestal is er een betere oplossing, eventueel met inheritance.
Mee eens, waarom zou je een alles omvattende list willen maken, wat is je doel? Het enige waar ik hiervan het voordeel kan vinden is een compiler, maar ik hoop dat je je daar niet zonder inlezen aan gaat wagen.

Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
.oisyn schreef op woensdag 12 mei 2010 @ 11:50:
std::vector<boost::any>
Nonsens, typeid(int) heeft ook gewoon een type_info instance. Idd, als je typeid() wilt aanroepen op een reference dan is het van belang dat dat type een vtable heeft, wat idd voor iets als int maar ook voor veel classes (zoals std::vector) niet opgaat.
Waarom? Dit voorbeeldje is legaal:
C++:
1
2
3
int a;
int& ra = a;
typeid(ra);

[ Voor 0% gewijzigd door MSalters op 12-05-2010 13:03 . Reden: code tag sluiten ]

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


Acties:
  • 0 Henk 'm!

Verwijderd

MSalters schreef op woensdag 12 mei 2010 @ 13:03:
[...]

Waarom? Dit voorbeeldje is legaal:
C++:
1
2
3
int a;
int& ra = a;
typeid(ra);
C++:
1
2
3
4
5
6
7
8
9
void* bla[3];
int a;
float b;
char tekst[40];

bla[0] = (void*)&a;
bla[1] = (void*)&b;
bla[2] = (void*)tekst;
typeid(bla[2]);

Veel succes...

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:51

.oisyn

Moderator Devschuur®

Demotivational Speaker

Wat hij zegt, dat was m'n punt. Behalve dan dat je een reference moet gebruiken wil je überhaupt een dynamische type lookup krijgen, en je een void* niet kunt dereferencen, maar dat terzijde.

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.


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 00:49
Rygir schreef op woensdag 12 mei 2010 @ 06:02:
Om ze nu in een list te bewaren in C++ lijkt mij de enige optie void* te zijn? Dus dat je enkel de pointer naar de data opslaat. Er is immers geen basisklasse voor de standaardtypes zoals int.
Er is geen basisklasse, maar als je zoiets nodig hebt, kun je die wel zelf maken. Als je een basisklasse hebt met een vtable (wat er op neerkomt dat je ergens een member, meestal op z'n minst een destructor, virtual hebt gedeclareert) dan kun je een object wél dynamisch casten naar het goede type, waarbij je in C++ een null-pointer krijgt waar je in Java een ClassCastException zou verwachten.

Je kunt dus eenvoudig een klasse maken die primitieve objecten kan vasthouden, bijvoorbeeld zo:
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
class Holder {
protected:
        // protected om te voorkomen dat deze class direct geïnstantieerd wordt
        Holder() { }
        // virtual om te zorgen dat subclasses correct vrijgegeven kunnen worden
        // via een base class pointer; dit levert gelijk een vtable op.
        virtual ~Holder() { }
};

class IntHolder : public Holder {
        int i;
public:
        IntHolder(int i) : i(i) { };
        int &get() { return i; }
};

class FloatHolder : public Holder {
        float f;
public:
        FloatHolder(float f) : f(f) { };
        float &get() { return f; }
};

class StringHolder : public Holder {
        std::string s;
public:
        StringHolder(std::string s) : s(s) { };
        std::string &get() { return s; }
};


Je kunt dan een vector van pointers naar Holder maken en de holders bij gebruik naar het juiste type casten:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
std::vector<Holder*> v;
v.push_back(new IntHolder(123));
v.push_back(new FloatHolder(3.1415f));
v.push_back(new StringHolder("foo"));

for (int i = 0; i < 3; ++i) {
        IntHolder    *ih = dynamic_cast<IntHolder*>(v[i]);
        if (ih) std::cout << "int: " << ih->get() << std::endl;
        FloatHolder  *fh = dynamic_cast<FloatHolder*>(v[i]);
        if (fh) std::cout << "float: " << fh->get() << std::endl;
        StringHolder *sh = dynamic_cast<StringHolder*>(v[i]);
        if (sh) std::cout << "string: " << sh->get() << std::endl;
}


Dit lijkt waarschijnlijk meer op wat je in Java zou doen. (En in Java werken vectors met primitives er in ook min of meer op deze manier, aangezien die eerst naar Integer, Float et cetera geconverteerd moeten worden, wat sinds JDK 1.5 ofzo automatisch gebeurt.)

Het voordeel ten opzichte van een getagte union of iets dergelijks is dat je niet een vaste lijst met enum waarden hebt; je kunt onbeperkt subclasses van Holder introduceren die niets met elkaar te maken hebben.

Verder kun je jezelf wat werk besparen door één template class te schrijven waarmee je effectief klassen kunt genereren voor types wanneer je ze nodig hebt. boost::any werkt volgens dat principe.

[ Voor 4% gewijzigd door Soultaker op 12-05-2010 13:49 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Zou je dat dan niet beter met een template oplossen, voordat je alle typen gaat herbouwen?

Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
.oisyn schreef op woensdag 12 mei 2010 @ 13:35:
Wat hij zegt, dat was m'n punt. Behalve dan dat je een reference moet gebruiken wil je überhaupt een dynamische type lookup krijgen, en je een void* niet kunt dereferencen, maar dat terzijde.
Nee hoor, ook daarvoor is een reference is niet nodig. Het enige wat je nodig hebt is een lvalue expression. Het standaard voorbeeld is natuurlijk typeid(*p), maar ook typeid(bla[2]) uit bovenstaand voorbeeld werkt in principe (alhoewel een slimme compiler natuurlijk dat type a priori kan bepalen; arrayelementen hebben een homogeen type en void* kan uberhaupt geen base class zijn)

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


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 00:49
@RoadRunner84:

Lees mijn laatste alinea even, wil je. ;)

De reden dat ik niet meteen met templates aan kwam zetten is dat de TS duidelijk uit een Java achtergrond komt, dus ik wilde niet allerlei geavanceerde C++ concepten op elkaar stapelen, maar eerst uitleggen hoe je in C++ een klassestructuur kunt creeëren met een common base class zoals je in Java ook hebt.

Templates zijn in C++ simpelweg een manier om compile-time klassen en functies te genereren. Dat verandert niets aan de werking van de gegenereerde klassen (je had ze ook zelf uit kunnen schrijven) maar als ik gelijk een complete template-based oplossing zou posten lijkt me de kans groot dat de TS de draad kwijt raakt, terwijl het principe net zo goed met gewone klassen geïllustreerd kan worden.

Acties:
  • 0 Henk 'm!

Verwijderd

Soultaker schreef op woensdag 12 mei 2010 @ 13:56:
@RoadRunner84:

Lees mijn laatste alinea even, wil je. ;)
Oeps, oke, had ik even overheen gelezen. Ik heb zelf een achtergrond vanuit C, C++ ken ik nauwelijks, maar ik wist toch wel dat templates bestaan... De werking is idd (ongeveer) hetzelfde als alle typen los definieren, alleen kan de compiler het wat compacter omzetten als je met templates werkt. Overigens kan ik me niet voorstellen dat anderen dit niet ook al gedaan hebben (het eerder genoemde boost lijkt er wel wat op), maar ik vraag me nog steeds af wat de TS er nou eigenlijk mee wil.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:51

.oisyn

Moderator Devschuur®

Demotivational Speaker

MSalters schreef op woensdag 12 mei 2010 @ 13:53:
[...]

Nee hoor, ook daarvoor is een reference is niet nodig. Het enige wat je nodig hebt is een lvalue expression.
Dat is dus precies wat ik bedoel. Thanks voor de verbetering, maar je ontkracht m'n punt er niet mee, namelijk dat een typeid op pointers niet zorgt voor een dynamic type lookup ;)
en void* kan uberhaupt geen base class zijn)
Maar het is suf dat je de typeid niet kunt opvragen van het type waar de void* naar wijst. Je kunt wel dynamic_casten als je het specifieke type weet, maar omdat een void* niet te dereferencen is kun je er geen type_info van krijgen.

Zoiets werkt dan weer wel:
C++:
1
2
3
4
5
6
7
8
9
10
struct A { virtual void foo() { } };
struct B : A { };

struct C { virtual void foo() { } };

int main()
{
    void * a = new B();
    std::cout << typeid(*static_cast<C*>(a)).name() << std::endl;
}

De typeid geeft op VC++ typeid(B ) terug, ookal gebruiken we een compleet ongerelateerde klasse met vtable om de dynamic type lookup te forceren. Maar goed, dit is volgens mij UB.

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.


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Bijzonder ernstige UB, ja. Dat het werkt is het gevolg van de Strong Law of Small Numbers. Het breekt waarschijnlijk wel, als je een iets complexere structuur hebt. Bijvoorbeeld een non-polymorphic base class (geen vtable op offset 0).

De achterliggende redenen zijn simpel en talrijk, bijvoorbeeld: het C++ typemodel is een bos, geen boom, en void* is dus niet de root. Om een object te kunnen deleten via een pointer, moet je óf het exacte objecttype weten, óf de pointer moet van een type met virtual dtor zijn, en dus is RTTI alleen nodig in het tweede geval.

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


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:51

.oisyn

Moderator Devschuur®

Demotivational Speaker

MSalters schreef op woensdag 12 mei 2010 @ 15:18:
Bijvoorbeeld een non-polymorphic base class (geen vtable op offset 0).
Duh, dan werkt dynamic_cast ook niet. Het rare is dat er een discrepantie zit tussen dynamic_cast en typeid wat void* aangaat. Mijn mening is dat als je een void* kan dynamic_casten, je ook een typeid zou moeten kunnen doen (die een exception gooit, bad_typeid bijvoorbeeld, als het geen polymorphic type betreft). De enige reden waarom dat niet kan is volgens mij gewoon omdat je een void* niet kunt dereferencen, ik zie niet direct een andere reden om een dergelijke feature te weren.
óf de pointer moet van een type met virtual dtor zijn, en dus is RTTI alleen nodig in het tweede geval.
Per se een virtual dtor? Ik dacht gewoon polymorph in het algemeen. Zo stond het ook in de 2003 versie van de standaard.

.edit:
5.2.8/2
When typeid is applied to an lvalue expression whose type is a polymorphic class type (10.3), the result refers to a type_info object representing the type of the most derived object (1.8) (that is, the dynamic type) to which the lvalue refers.

10.3/1
Virtual functions support dynamic binding and object-oriented programming. A class that declares or inherits a virtual function is called a polymorphic class.
Dit even los van het feit dat het in het algemeen natuurlijk wel een goed idee is om een polymorphic class een virtual dtor te geven.

[ Voor 38% gewijzigd door .oisyn op 12-05-2010 15:43 ]

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.


Acties:
  • 0 Henk 'm!

Verwijderd

MSalters schreef op woensdag 12 mei 2010 @ 15:18:
Bijzonder ernstige UB, ja. Dat het werkt is het gevolg van de Strong Law of Small Numbers. Het breekt waarschijnlijk wel, als je een iets complexere structuur hebt. Bijvoorbeeld een non-polymorphic base class (geen vtable op offset 0).

De achterliggende redenen zijn simpel en talrijk, bijvoorbeeld: het C++ typemodel is een bos, geen boom, en void* is dus niet de root. Om een object te kunnen deleten via een pointer, moet je óf het exacte objecttype weten, óf de pointer moet van een type met virtual dtor zijn, en dus is RTTI alleen nodig in het tweede geval.
void* is niet de root, er is namelijk geen (echte) root. Maar ieder type kan gedereferenced worden met het & teken, wat een <type>* oplevert. En ieder <type>* kan gecast worden naar een void*. Helaas heb je dan alleen nog maar een geheugenplek over, als daar nou een array zat moet je dus ergens onthouden hebbne hoe groot dat array was, anders heb je daar geen idee van. Voor een object zal je dan dus een pointer naar de destructor moeten hebben en een pointer naar het object.
Stel dat er een pointer naar het object void* obje bestaat, en een pointer naar de destructor van datzelfde object (die dus weet van welke klasse die is, per definitie) void* destr. Dan kan ik (smerig, achter de schermen) de destructor destr aanroepen met als parameter obje, de gewone vorm obje->destr() of detele obje zal niet werken, omdat obje geen object is, maar een (void) pointer er naartoe.

Ik wil nog steeds weten wat de TS er nou mee wil (hij heeft nog niet gereageerd).

Acties:
  • 0 Henk 'm!

  • Rygir
  • Registratie: Mei 2004
  • Laatst online: 01-03 05:45
Dan zal ik dat nu even proberen uit te leggen :
Vergelijkbaar met hoe men generics met getemplatiseerde types gebruikt voor de klassen toe te laten "alles" op te slaan, maar slechts van 1 type tegelijk zou ik nu graag een herbruikbare, d.w.z, zonder aanpassingen ooit te moeten doen aan die code (heel de point van generics en OO, mijns inziens), klasse maken voor de opslag van gegevens van meerdere ongerelateerde typen.

Specifiek wil ik ze opslaan met een naam, dus dat ik kan zeggen "geef mij variabele met naam fred", en dan kijkt in zijn hashtable of welk ADT ik ook aanhoud en geeft de corresponderende binaire data terug. Nu moet ik alleen nog verifiëren of "fred" wel van een datatype is dat ik verwacht, zodat ik het ook effectief kan assignen.


Misschien duidelijker met volgend voorbeeld, ik heb een programma met allerlei parameters (duh), laten we zeggen venstergrootte (een paar ints) en vensternaam (een string) met daarin een 3D object (een troep floats). Het eerste is een vast datatype (kan ik inderdaad inpakken in een andere klasse), het tweede is een klasse waar ik geen controle over heb (kan ik in principe ook wel inpakken maar dan wordt het een beetje belachelijk imho), het laatste hangt er van af welke libraries ik gebruik of dat ik het zelf maak.

Nu heb ik dat (hypothetisch) opgeslagen in een XML bestand, met
code:
1
<object datatype=int>123</object>
of vergelijkbare structuur. Dus elke variabele heeft een gekend type en elke variabele heeft een waarde. Uiteraard als ik dat parse en verwerk en de conversie doe van string naar int bvb en de relevante data construeer wil ik dat geen dozijn keer gaan doen, dus ik bewaar die data nu. Zo wil ik alle gegevens die ik ingelezen heb uit een bestand representeren met de correcte binaire structuren zoals ik ze van dan af wil gebruiken, al onmiddelijk na het parsen van mijn bestand.

Nu heb ik dus een hoop binaire data die allemaal gecentraliseerd zit rondom mijn XML inleesstructuur (of wat voor databron ook, toetsenbord op console parameters instellen, maakt niet uit). Ik kan die niet op voorhand verspreiden want ik weet nog niet waarheen, het is immers niet verboden "nonsens" parameters in de file te zetten die nergens worden gebruikt, bijvoorbeeld ivm backwards compatibility. Dus nu zit ik met een heleboel gegevens uit een verwerkt document van types die ik op het moment van maken van de klasse nog niet ken, en dus geen rekening mee kan houden expliciet.

Verduidelijkt dat het idee?

[ Voor 9% gewijzigd door Rygir op 12-05-2010 20:56 . Reden: opmaak fixen ]


Acties:
  • 0 Henk 'm!

  • MTWZZ
  • Registratie: Mei 2000
  • Laatst online: 13-08-2021

MTWZZ

One life, live it!

@Rygir:
Als ik je goed begrijp heb je in jouw programma data nodig uit verschillende bronnen afkomstig kan zijn, hetzij config file, command line arguments of een ananas.
Is het dan niet een idee om de opslagstructuur te realiseren als een XML store en verschillende providers te maken die de structuur kunnen vullen. Aan de kant van jouw applicatie zou je dan een wrapper class om die XML kunnen bouwen waaruit je de gewenste data kan halen.

Stel dat je een class DataStore hebt met daarop een aantal methodes als ReadInt, ReadString, ReadBytes etc.
Als argument nemen die bijvoorbeeld een XPath expressie of iets wat je zelf handig vindt (bijvoorbeeld "Applicatie.Submodule.Parameter").

Pseudo code:
code:
1
2
3
4
dataStore.ReadFromConfig("eenConfigFile.xml");

int foo = dataStore.ReadInt("Applicatie.SubModule.Foo");
float bar = dataStore.ReadFloat("Applicatie.SubModule2.Bar");

[ Voor 12% gewijzigd door MTWZZ op 12-05-2010 21:33 ]

Nu met Land Rover Series 3 en Defender 90


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:51

.oisyn

Moderator Devschuur®

Demotivational Speaker

Oftewel, je wilt een std::map<std::string, boost::any>, of iets vergelijkbaars.

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.


Acties:
  • 0 Henk 'm!

  • Rygir
  • Registratie: Mei 2004
  • Laatst online: 01-03 05:45
MTWZZ schreef op woensdag 12 mei 2010 @ 21:31:
@Rygir:
Is het dan niet een idee om de opslagstructuur te realiseren als een XML store en verschillende providers te maken die de structuur kunnen vullen. Aan de kant van jouw applicatie zou je dan een wrapper class om die XML kunnen bouwen waaruit je de gewenste data kan halen.
Ja, behalve dat de bedoeling eigenlijk net was om de overhead van (last minute) parsing (in dit geval van XML) weg te halen.
Stel dat je een class DataStore hebt met daarop een aantal methodes als ReadInt, ReadString, ReadBytes etc.
Dat lijkt een beetje op wat ik er van zou willen maken, al was ik aan het *hopen* dat het op te lossen was zodat ik er DataStore.Read(Type asType); van zou kunnen maken, waarbij Type pseudocode is voor wat in java een class zou zijn, maar in C... een ramp ;) .
.oisyn schreef op donderdag 13 mei 2010 @ 00:29:
Oftewel, je wilt een std::map<std::string, boost::any>, of iets vergelijkbaars.
Bingo, denk ik, want ik heb nog nooit met boost gewerkt (als iemand daar toevallig een pracht van een tutorial voor van buiten weet is dat altijd mooi meegenomen); maar dan nog zou ik eigenlijk willen weten hoe boost het dan doet, die gaan uiteindelijk toch iets met standaard C moeten doen?

Zoals in de eerste antwoorden ben ik zelf ook wantrouwig ben tegenover constructies waarbij je niet weet welk type je eigenlijk krijgt, als ik me niet vergis zou je dat tot de "bad smells" - in de zin van anti-patterns - kunnen rekenen. Daarom ben ik ook curieus naar hoe anderen "config files" afhandelen, dat lijkt me iets dat in zo'n beetje elk project van pas komt en toch heb ik daar nog nooit iets over gelezen of gehoord. Precies alsof iedereen zijn variabelen hardcoded er in steekt en/of het allemaal maar uit een database fetched of zo ;) .

[ Voor 38% gewijzigd door Rygir op 13-05-2010 06:42 . Reden: 2e post nog replyen ]


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

boost.program_options :) (QSettings/QVariant)

[ Voor 30% gewijzigd door Zoijar op 13-05-2010 09:28 ]


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Misschien is double dispatch ook een deel van je oplossing.

Hoe dan ook is je complex type altijd uiteen te halen tot er uiteindelijk enkel primitieve types overblijven. Je kan dus altijd klasse-representaties schrijven voor elk deel.
Je kan Soultaker's aanpak uitbreiden met

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Storage
{
  std::map<std::string, boost::shared_ptr<Holder> > Types;
  public:
    template<class Target>
    boost::shared_ptr<Target> Read(std::string Name) const
    { return boost::dynamic_pointer_cast<Target>(Types[Name]); }
};

Storage st;
boost::share_ptr<IntHolderint> i = st.Read<IntHolder>("foo");
if (i)
  std::cout << "foo = " << i->get();

[ Voor 11% gewijzigd door H!GHGuY op 13-05-2010 11:32 ]

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • Rygir
  • Registratie: Mei 2004
  • Laatst online: 01-03 05:45
H!GHGuY schreef op donderdag 13 mei 2010 @ 11:31:
C++:
1
2
3
4
5
6
7
8
class Storage
{
  std::map<std::string, boost::shared_ptr<Holder> > Types;
  public:
    template<class Target>
    boost::shared_ptr<Target> Read(std::string Name) const
    { return boost::dynamic_pointer_cast<Target>(Types[Name]); }
};
Als ik dat lees moet ik toch nog altijd weten wat Target moet zijn, het probleem is dat ik niet "weet" of ik nu met een Int of een ander type, dat moet ik ergens in de lijst kunnen opslaan. En als ik zelf een code verzin voor elk type, dan moet ik die uitbreiden bij elk type dat gebruikt moet kunnen worden. En ik weet niet welke dat allemaal zullen zijn, dat is net het issue. Nu was ik aan het hopen dat een compiler sowieso voor elk type een bepaalde code heeft, die ergens wordt opgeslagen, zodat ik die kan opvragen en opslaan in mijn lijst eventueel, en dan een check doen of dat dat hetzelfde is als mijn variabele type waaraan ik wil assignen, vervolgens assign ik, en daarmee zit de waarde in mijn var waar hij thuishoort. Daar bedoel ik mee, als ik eenmaal kan uitvogelen welk type het is, dan kan de storage klasse inderdaad voorzien worden van een method met een getemplatiseerd type die controleert of de gevraagde waarde van het juiste type is alvorens te assignen, en anders een error throwed. Het is de stap van het "uitvissen welk type ik heb opgeslagen op een native C manier" die ik niet beheers... en ik heb nog niet begrepen hoe boost:any daarbij helpt :s

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:51

.oisyn

Moderator Devschuur®

Demotivational Speaker

Dat uitvissen kan dus op basis van de typeinfo, die je dan wel moet storen op het moment dat je het type wél weet (dus als je 'm vult met dee waarde). boost::any werkt ook zo, en ook de Holder interface van Soultaker kan het ondersteunen met een kleine aanpassing (heb de dtor ook meteen even public gemaakt, het was niet zo handig van Soultaker om die ook protected te maken ;), en de class is door de verandering ook abstract, dus de lege protected ctor kan sowieso weg):
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Holder { 
protected: 
    // protected om te voorkomen dat deze class direct geïnstantieerd wordt 
    Holder() { } 

public:
    // virtual om te zorgen dat subclasses correct vrijgegeven kunnen worden 
    // via een base class pointer; dit levert gelijk een vtable op. 
    virtual ~Holder() { } 

    // om het type mee op te vragen
    virtual const std::type_info & type() const = 0;

    // om de waarde te kunnen kopiëren, deze is later nodig
    virtual Holder * clone() const = 0;
};

// generieke Holder implementatie voor een willekeurig type
template<class T> class GenericHolder : public Holder
{
public:
    GenericHolder(const T & v = T()) : value(v) { }
    T & get() { return value; }
    const T & get() const { return value; }

    const std::type_info & type() const { return typeid(T); }
    GenericHolder * clone() const { return new GenericHolder(*this); }

private:
    T value;
};

// helper functie om makkelijk een GenericHolder te alloceren
template<class T> GenericHolder<T>* new_holder(T value)
{
    return new GenericHolder<T>(value);
}

int main()
{
    std::vector<Holder*> values;
    values.push_back(new_holder(23));
    values.push_back(new_holder(std::string("aap")));
    values.push_back(new_holder(&values));

    for (unsigned i = 0; i < values.size(); i++)
    {
        std::cout << "values[" << i << "] is a ";

        if (values[i]->type() == typeid(int))
            std::cout << "int";
        else if (values[i]->type() == typeid(std::string))
            std::cout << "string";
        else
            std::cout << values[i]->type().name();

        std::cout << std::endl;
    }
}

Uitvoer (op VC++ 2008):
values[0] is a int
values[1] is a string
values[2] is a class std::vector<class Holder *,class std::allocator<class Holder *> > *


Maar het werken met losse Holder interfaces is niet erg handig. Je wilt liever een container class die voor jou met de interface dealt en de nodige casts doet bij het opvragen van de waarde. Laten we die class 'Any' noemen ;)
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// container die het werken met Holder instances wegabstraheert
class Any
{
public:
    // dummy type voor de type() functie
    struct empty_type;

    // empty ctor
    Any()
    :   pValue(0)
    {
    }

    // copy ctor
    Any(const Any & other)
    :   pValue(other.empty() ? 0 : other.pValue->clone())
    {
        
    }

    // conversion ctor om een Any van een waarde te construeren
    template<class T>
    Any(const T & value)
    :   pValue(new_holder(value))
    {
    }

    // copy assignment operator
    template<class T>
    Any & operator=(const Any & other)
    {
        set(other.empty() ? 0 : other.pValue->clone());
        return *this;
    }

    // om een willekeurige waarde te kunnen assignen
    template<class T>
    Any & operator=(const T & value)
    {
        set(new_holder(value));
        return *this;
    }

    // dtor
    ~Any()
    {
        set(0);
    }

    // maakt 'm weer leeg
    void reset()
    {
        set(0);
    }

    // geeft aan of ie leeg is
    bool empty() const
    {
        return pValue == 0;
    }

    // geeft de huidige waarde terug (of gooit std::bad_cast indien typen niet overeenkomen)
    template<class T>
    T & get()
    {
        if (type() != typeid(T))
            throw std::bad_cast();

        return static_cast<GenericHolder<T>*>(pValue)->get();
    }

    // idem, maar const versie
    template<class T>
    const T & get() const
    {
        return const_cast<Any*>(this)->get();
    }

    // vraagt het huidige type op
    const std::type_info & type() const
    {
        return pValue ? pValue->type() : typeid(empty_type);
    }

private:
    void set(Holder * v)
    {
        if (pValue != v)
        {
            if (pValue)
                delete pValue;
            pValue = v;
        }
    }

    Holder * pValue;
};

int main()
{
    std::vector<Any> values;
    values.push_back(23);
    values.push_back(std::string("aap"));
    values.push_back(&values);

    for (unsigned i = 0; i < values.size(); i++)
    {
        std::cout << "values[" << i << "] is a ";

        if (values[i].type() == typeid(int))
            std::cout << "int";
        else if (values[i].type() == typeid(std::string))
            std::cout << "string";
        else
            std::cout << values[i].type().name();

        std::cout << std::endl;
    }

    try
    {
        std::cout << "values[0]: ";
        int i = values[0].get<int>();
        std::cout << i << std::endl;

        std::cout << "values[1]: ";
        i = values[1].get<int>();
        std::cout << i << std::endl;
    }
    catch(std::bad_cast)
    {
        std::cout << "exception: bad_cast" << std::endl;
    }
}


En ja, boost::any werkt vergelijkbaar :). Ik zou wel aanraden die te gebruiken ipv de code hier, die is iig uitgebreider (zoals operator== e.d.) en allicht ook een stuk efficienter, al heb ik dat nooit geverifieerd. Ik heb wel ooit eens een eigen vergelijkbare implementatie gebruikt met een kleine vaste interne buffer zodat er voor kleine types als int geen geheugenallocaties nodig zijn.

[ Voor 5% gewijzigd door .oisyn op 14-05-2010 11:40 ]

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.


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Rygir schreef op vrijdag 14 mei 2010 @ 01:18:
[...]

Als ik dat lees moet ik toch nog altijd weten wat Target moet zijn, het probleem is dat ik niet "weet" of ik nu met een Int of een ander type, dat moet ik ergens in de lijst kunnen opslaan. En als ik zelf een code verzin voor elk type, dan moet ik die uitbreiden bij elk type dat gebruikt moet kunnen worden. En ik weet niet welke dat allemaal zullen zijn, dat is net het issue. Nu was ik aan het hopen dat een compiler sowieso voor elk type een bepaalde code heeft, die ergens wordt opgeslagen, zodat ik die kan opvragen en opslaan in mijn lijst eventueel, en dan een check doen of dat dat hetzelfde is als mijn variabele type waaraan ik wil assignen, vervolgens assign ik, en daarmee zit de waarde in mijn var waar hij thuishoort. Daar bedoel ik mee, als ik eenmaal kan uitvogelen welk type het is, dan kan de storage klasse inderdaad voorzien worden van een method met een getemplatiseerd type die controleert of de gevraagde waarde van het juiste type is alvorens te assignen, en anders een error throwed. Het is de stap van het "uitvissen welk type ik heb opgeslagen op een native C manier" die ik niet beheers... en ik heb nog niet begrepen hoe boost:any daarbij helpt :s
Je moet toch _altijd_ weten wat Target moet zijn? Ik ken weinig applicaties die input in elk willekeurig formaat kunnen interpreteren en er iets zinnigs mee doen binnen het domein van de input-data. Ergens moet je een formaat hanteren waar parameter X een int is en parameter Y een string.

Let ook op de dynamic_pointer_cast: deze retourneert een NULL-shared_ptr als het type niet correct is. Dus je kan gewoon zeggen:
C++:
1
2
3
// Parameter X moet een int zijn en Y een string:
assert(st.Read<int>("X"));
assert(st.Read<std::string>("Y"));

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:51

.oisyn

Moderator Devschuur®

Demotivational Speaker

Behalve dan dat je IntHolder en StringHolder bedoelt ipv int en std::string ;)

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.

Pagina: 1