void*-Umwandlung
-
Hallo,
Wenn ich einen Pointer
void* bla;deklariere, kann ich den dann später zu einem beliebigen Typ casten? Falls ja, wie und zu welchen Typen (geht z. B. auch eine Umwandlung in std::string)?
-
In C++ kann man wild umhercasten, das ist immer möglich. Nur ob es sinnvoll ist?
std::string str; void* p = &str; std::string* p2 = reinterpret_cast<std::string*>(p);Im allgemein sei aber gesagt,
void*ist ein Designfehler. In C++ kann man darauf so gut wie verzichten und erst recht, sollte man nicht vonvoid*auf irgendeinen Typ casten. Sowas ist sehr Fehleranfällig und sollte unbedingt vermieden werden!In C kommt man nur schwer bis gar nicht da rum, aber wir sind ja nicht in C

Grüssli
-
Vieles lässt sich in C++ durch Laufzeitpolymorphie lösen (virtuelle Funktionen), und sonst gibts z.B. auch Boost.Any und Boost.Variant...
-
Nexus schrieb:
Vieles lässt sich in C++ durch Laufzeitpolymorphie lösen (virtuelle Funktionen), und sonst gibts z.B. auch Boost.Any und Boost.Variant...
Vieles lässt sich auch durch Complizeit-Polymorphie lösen. Siehe z.B. std::sort im Vergleich mit qsort aus cstdlib.
-
Ja, kommt eben auf die konkrete Anwendung drauf an.
In C++ hat man mit den Templates und dem STL-Algorithmen-Prinzip viele Möglichkeiten, die sich auch noch mit Laufzeitpolymorphie, eigenen Operatoren und Funktionsüberladung kombinieren lassen. Auch etwas, was ich sehr an der Sprache mag...

-
Nexus schrieb:
... sonst gibts z.B. auch Boost.Any und Boost.Variant...
Danke für den Tipp, boost.Variant sieht sehr gut aus!
-
Viel interessanter finde ich eigentlich die Frage, warum Anfänger meistens als erstes nach "Alles-Typen" und "Alles-Funktionen" suchen.
Vermutlich drückt sich da eine innere "Weiß-auch-noch-nicht-was-ich-eigentlich-will"-Haltung aus.Dabei bedeutet "Programmieren" eigentlich "Entscheidungen treffen, was der Computer tun soll".
Es ist gerade eine Stärke einer Programmiersprache, dass man mit ihr semantische Zusammenhänge ausdrücken (also "Aussagen treffen") kann;
z.B. "int i;" = "Ich will mit einer Ganzzahl arbeiten und nenne sie i"Was ist wirklich gewonnen mit einem "ich will irgendwas mit irgendwas tun" (wie man das mit void* tut) ?
Damit will ich nicht behaupten, dass es NIE der richtige Weg sein kann, aber ich bin doch erstaunt, wie viele Anfänger spontan auf diesen Weg zusteuern - und wie schnell.Gruß,
Simon2.
-
Diese fast schon philosophische Frage hat mich auch schon beschäftigt.

Wahrscheinlich hat das auch damit zu tun, dass man als Anfänger noch nicht wie ein Programmierer denkt. Vielmehr versucht man, Aspekte aus dem Leben in irgendeiner Form auf den Computer zu übertragen (auch in der OOP äussert sich das teilweise). Und da man im Leben andere Vorstellungen von Worten, Zahlen, Objekten hat, versucht man vielleicht, auch in der Programmierung möglichst flexibel zu bleiben - ein Ansatz kann dabei sein, sich möglichst nicht auf einen Datentypen zu beschränken. Dazu kommt, dass man sich als Anfänger gar nicht über die Typsicherheit in C++ im Klaren ist (vielleicht nicht zuletzt, weil es nicht in allen Programmiersprachen so ist).
Dies alles zusammen kann, möglicherweise, zu jenem Verhalten führen.

-
Also ich stand auch vor dem Problem, verschiedene Datentypen durch eine Queue zu pressen. Hab das dann so gelöst, dass ich eine Basisklasse entwickelt habe und mehrere Klassen davon abgeleitet habe. Wenn ich dann ein Datenobjekt in die Queue ( bsp. zum Transport ) quetsche, dann caste ich das vorher in die Basisklasse und dann passt das.
Mir ist bisher kein Fall bekannt, wo void* notwendig war....
-
void* ist verlockend wenn man zB
- nichts von Vererbung und Basisklassen weiß
- nichts von Prototyping weiß
- nichts von Templates weiß
-
Wie würdet ihr folgendes Problem sonst lösen: Ein Benutzer kann in einer Datei u. a. einen der vier Datentypen float, int, string und bool angeben (wieso er das können soll, führt an dieser Stelle zu weit). Wie kann ich mit so etwas im Programm nun umgehen, ohne vier Variablen anzulegen (von jedem Typ eine), von jeder Methode jeweils vier Varianten anlegen (je eine für einen Typen) usw. und dann jedes mal mit Kontrollanweisungen den angegebenen Typ abfragen zu müssen usw.?
Mit boost.Variant kann ich mir ein Objekt
boost::variant<int, float, bool, string> res;anlegen und dies als Schnittstellen-Parameter verwenden und mir dann passende Visitors basteln oder einfach boost::get<> verwenden.
Es gibt also tatsächlich Sonderfälle, wo man selber nicht darüber bestimmt, auf was für einem Typ man arbeiten wird.
-
TheBrain schrieb:
(wieso er das können soll, führt an dieser Stelle zu weit).
Genau das ist aber das Entscheidende.
Felix
-
Oder wie mein Projektleiter zu sagen pflegt:
"Wenn du das genau so machen musst, dann ist an deinem Konzept was falsch ;)"
Kurzum: Durch Änderung des Konzepts oder der Programmstruktur kann man meist solche Probleme sauber umgehen.
-
Bei sowas ist oft laufzeit polymorphie das richtige. Eine abstrakte Klasse "EingegebeneDaten" und dann jeweils IntegerDaten, BoolDaten,... ableiten.
-
void* können übrigens auch äußerst bösartig sein, wenn mann nicht exakt dasselbe wieder rausholt, was man vorher reingepackt hat.
Z.B.:
struct A { int something; }; struct B { virtual void someFunc(); }; [...] B b; b.something = 1234567890; void* v = &b; A* a = reinterpret_cast<A*>(v); // autsch! cout << a->something; // möglicherweise Müll, ist undefiniertIn dem Beispiel würde die Vererbungsbeziehung zwischen A und B versteckt und der Compiler kann die möglicherweise notwendige Pointer-Korrektur (vtableptr könnte z.B. erst durch B vor A hinzugefügt werden) nicht durchführen. Das trifft auch bei einem Upcast (so wie im Bsp) zu!
Gefährliche Geschichte, Finger weg.

-
dafür braucht man aber auch nicht unbedingt void-zeiger, die gefahr hier steckt im reinterpret_cast. mit void* + static_cast lässt sich diese gefahr aber prächtig nachbilden.
-
TheBrain schrieb:
...Ein Benutzer kann in einer Datei u. a. einen der vier Datentypen float, int, string und bool angeben ...
Das bedeutet aber doch nicht, dass Du hier einen "Alles-Datentypen" verwenden musst.
Gerade Eingaben liest man oft erstmal als string ein und interpretiert dann die weitere Vorgehensweise.
Sieh es mal so: Zwar hast Du für die verschiedenen Tyen dieselbe Eingangsschnittstelle, aber mit einem bool wirst Du (nach dem Einlesen) nicht dasselbe machen wie mit einem string.
Insgesamt ist es eine gute Idee, das Einlesen von Daten von der späteren Verarbeitung zu trennen (allein schon, um Inputfehler nicht durch die gesamte Fachlichkeit zu schleifen) und Du kommst sowieso an den Punkt, an dem Du wissen und berücksichtigen musst, welchen Datentyp Du da eigentlich gerade eingelesen hast.Für Deinen Fall (mal ohne Fehlerprüfungen) die ganz einfache Variante:
void verarbeite(int); void verarbeite(float); void verarbeite(bool); void verarbeite(string); ifstream in("inputfile"); string typkenzeichen; in >> typkennzeichen; switch(typkennzeichen[0]) { case 'i': int i; in >> i; verarbeite(i); break; case 'f': float f; in >> f; verarbeite(f); break; case 's': string s; in >> s; verarbeite(s); break; case 'b': bool b; in >> b; verarbeite(b); break; default: cerr << "unbekanntes Typkennzeichen\n"; }Für größere Mengen an Datentypen kann das Ganze auch noch ausgefeilter machen (mit templates, maps, polymorphen Wrapperklassen, ....), aber die Grundidee "Typbestimmung, typsepzifisches Einlesen&Verarbeiten" ist immer dieselbe (gute).
Gruß,
Simon2.