C++ Datentypen in einer Variablen speichern?
Hallo,
ich habe aktuell folgendes Problem:
Ich möchte eine Klasse schreiben, mit der einer verkette Liste unterschiedlicher Datentypen realisiert werden kann.
Das klappt auch alles, jedoch muss ich im Zuge dessen alle Typen (per Zeiger übergeben) in einen typlosen void Zeiger umwandeln.
Beim Zugriff fehlt dann die Information, welcher Datentyp es einmal war:
Format:
struct node{ node *prev{nullptr}; void *elem{nullptr}; node *next{nullptr}; };
Gibt es eine Möglichkeit, einen Datentypen so in der Struktur zu speichern, dass ich den void * später mithilfe dieses in den typen "zurückcasten" kann?
Das alles zur Compiletime stattfinden müsste ist mir klar und eigentlich nicht unmöglich, ich habe nur keine Ahnung wie.
Gedacht wie folgt:
template<typename T>
auto xy(T&&)
{
typesaver savethistype = T;
return *static_cast<savethistype *>(void_ptr);
}
Mit freundlichen Grüßen
Lukas Zander
2 Antworten
Mach doch einfach eine Liste, der du als Template-Parameter den Typ übergibst.
Oder du erstellst für jeden Datentyp eine eigene Subklasse und speicherst die gemeinsame Baseclass in der Liste.
Was davon?
Ersteres im Prinzip:
template<T>
struct node{
node *prev{nullptr};
T *elem{nullptr};
node *next{nullptr};
};
Aber wenn ich node jetzt instanziiere muss ich den typen kennen und der soll ja von mal zu mal unterschiedlich sein
Du hast deinen Code erst im Nachhinein hionzugefügt. Da würde man das template zum Listentyp hinzufügen.
Und ja, na klar musst du den Typ dabei angeben.
und der soll ja von mal zu mal unterschiedlich sein
Ja dann brauchst du die zweite Variante.
Da machst du es so wie hier:
https://stackoverflow.com/questions/18453145/how-is-stdfunction-implemented
Also im Prinzip hast du eine Baseclass, die du in der Liste speicherst und alle Objekte ereben eben von der Baseclass.
Alternativ könntest du auch eine Reihe von Typen angeben mit std::variant:
Dann kann ich aber nur eingebaute Datentypen abdecken und nicht Benutzerdefinierte Klassen, oder?
In der ersten Lösung, das was ich dir mit std::function verlinkt habe, können auch benutzerdefinierte Typen abgedeckt werden.
Ok, ich muss jedoch zugeben, dass ich da bisher nur bahnhof verstanden habe
Im Prinzip hast du einfach nur eine Baseclass, die du in der Liste speicherst. Für jeden neuen Typ, den du speichern möchtest, erstellst du eine neue Klasse, die von der Baseclass erbt und speicherst diese in der Liste.
Das erstellen der neuen Klasse geschieht automatisch via Templates und einer Allokation auf dem Stack:
struct node_base {
virtual ~node_base() {}
};
template <typename T>
struct node : node_base {
T element;
node(T element) : element(element) {}
};
Und in deiner Liste hast du dann zum Beispiel:
template<typename T>
void add(T element){
this->append(new node(element));
}
Es sei aber anzumerken, dass dir das nur bedingt etwas bringt, denn du kannst nur Funktionen der Baseclass aufrufen oder du müsstest den Typ des jeweiligen Eintrags kennen um das zurück zu casten.
Ok, das seh ich grad. Außerdem speichert er einen einheitlichen Datentypen
Genau.
Je nachdem, was du machen möchtest, musst du dann entweder ein einheitliches Interface implementieren oder auf std::variant oder dergleichen zurückgreifen.
Templates wären da eine möglichkeit.
An sich ist denke ich die Datenstruktur keine sinnvolle. Weil man dann ja im Endeffekt wissen muss welches Glied in der Kette welchen Typ hat. Oder eben immer raten muss welchen Typ diese Liste hat.
Ich würde das ganze warscheinlich wenn ich so etwas überhaupt machen wollen würde. Innerhalb der Listenelemente Kapseln.
Sprich: ich hab ne Klasse Chainlistelement. Die halt jeweils ne Referenz auf das vorherige oder nächste listen element.
Und hat gleichzeitig auch ein Value Feld welches den eigentlichen wert hält.
So haste die Liste und ihre Datentypen schonmal getrennt.
Hier kann man dann ggf. Mit Templates arbeiten. Wenn du halt richtig geraten hast und die value sich casten lässt kriegste die raus. Ansonsten halt null.
Alternativ kannste die value auch einfach vom Typ object lassen und die lässt den Benutzer casten.
Alternativ: kannste auch mehrere value felder haben für die unterstützen Typen.
Oder du leitest für jede unterstützte value ne eigene element Klasse ab. Z.b. die Klasse InegerChainElement für eben integer Werte.
Im Endeffekt aber: wenn du mehrere Datentypen in einer Liste haben willst. Dann kommst du nur mit compilezeit möglichkeiten nicht drumherum das du "raten" musst welchen Typ das Element hat. (Genau deswegen ist es nach meiner Ansicht keine gute Datenstruktur.)
In C# haste noch die laufzeitmöglichkeit der reflection. Was das C++ äquivalent ist weiss ich nicht. Und in einer simplen Liste mit reflection zu arbeiten ist nicht so pralle.
Falls das was du vorhast nicht rein zur übung ist und eventuell für etwas benutzt werden soll. Dann würde ich eher dazu raten deine Datenstruktur zu überdenken.
So haste die Liste und ihre Datentypen schonmal getrennt.
Gleichzeitig aber auch mehr Indirektion.
Kannst du mir das in diesem Kontext Mal erklären? Ggf. An einem Beispiel?
Vielen Dank.
Wie genau geht das?
Ich stelle mir das aktuelle so vor: