std::vector<T> als std::vector<U> interpretieren
-
Nexus schrieb:
Der Versuch, den ganzen Speicher von
std::vector
zu reinterpretieren, wird dir nicht viel bringen. Du wirst um elementweises Casten nicht herumkommen.Es ist jedoch gut möglich, dass sich dein Problem auf eine elegantere Weise lösen lässt. Dazu ist es jedoch zu wenig genau beschrieben...
daten 1: type_char,1,2,15,-39,12 daten 2: type_int,1232,439823,2,-3,1284 daten 3: type_float,23.3,-12.2,3.4
ich bekomme diese daten jeweils als std::vector<char> und muss sie so interpretieren, wie es bei jedem datenblock angegeben wurde. die größe der daten ist nicht beschränkt, zwischen 1 byte und 2 gbyte wäre alles denkbar.
'daten 2' könnte man einfach so interpretieren:
int* idata = reinterpret_cast<int*>(&data[1]); std::size_t count = data.size() / sizeof(int);
damit hat man einen passenden zeiger auf die ints, und die anzahl der werte ist auch bekannt. komfortabel ist es allerdings nicht.
ich hätte lieber einen container, mit dem ich die daten einfach weiter verarbeiten kann.
ich könnte die werte lesen und in einen neuen, passenden vector stecken. dann habe ich aber vollkommen identische daten doppelt im speicher. das würde ich gerne vermeiden.
-
victor schrieb:
ich bekomme diese daten jeweils als std::vector<char>
die daten liegen als uncodiertes binärformat vor, nicht als ascii-string mit kommatrennzeichen (wie man aus meiner beschreibung vielleicht vermuten könnte). 'type_char' ist bspw. ein char mit dem wert 0, die nutzdaten folgen unmittelbar danach, ohne trennzeichen.
-
victor schrieb:
ich könnte die werte lesen und in einen neuen, passenden vector stecken. dann habe ich aber vollkommen identische daten doppelt im speicher. das würde ich gerne vermeiden.
Du könntest den alten vector mit den unpassenden Daten löschen, dann hast du sie nur zwischendurch doppelt im Speicher.
std::vector<char> alter_vec; /* aus alt mach neu... */ alter_vec.swap(std::vector<char>()); //vertausche den inhalt mit dem eines temporaeren vectors - der nimmt dann die ganzen chars mit ins Grab
-
Reihenfolge beachten
std::vector<char>().swap(alter_vec);
da rvalues nicht an non-const Referenzen gebunden werden können.
-
die kopie würde mir echt wehtun. zum einen soll das ganze auf kleinen geräten mit begrenztem speicher laufen, wo die kopie vielleicht nicht mehr in den speicher passt.
zum anderen fühlt es sich einfach nicht richtig an, eine exakte kopie der daten zu machen, nur zu dem zweck sie anders interpretieren zu können. die daten sind ja schon da und sie liegen im passenden format vor - warum also kopieren?ich habe schon daran gedacht, einen eigenen container zu implementieren, der wie ein
const std::vector
funktioniert, aber keinen eigenen speicher hat. lesezugriffe könnte ich damit wohl effizient durchführen, fürs schreiben müsste dann halt eine kopie gemacht werden.
hört sich das nach einem vernünftigen plan an?
-
victor schrieb:
ich habe schon daran gedacht, einen eigenen container zu implementieren, der wie ein
const std::vector
funktioniert, aber keinen eigenen speicher hat. lesezugriffe könnte ich damit wohl effizient durchführen, fürs schreiben müsste dann halt eine kopie gemacht werden.
hört sich das nach einem vernünftigen plan an?Du meinst also eine Art Wrapper, der Container automatisch uminterpretiert? Wäre vielleicht schon eine Lösung. Oder halt eine Funktion, die ein Element bestimmt interpretiert; kommt halt drauf an, wie häufig du uminterpretiert lesen willst.
Aber ist dir der
std::vector
bereits fix gegeben? Sonst könntest du das Problem vielleicht schon früher etwas eleganter lösen.
-
In Abhängigkeit davon, wieviel eigenen Code du schreiben willst, gibt es verschiedene Möglichkeiten. Die einfachste Variante besteht darin, einfache get-Funktionen zu schreiben, die die Daten nur bei Bedarf entsprechend kopieren:
template<typename T> T from_vector(const std::vector<char>& v, std::size_t index) { assert( is_correct_type<T>() ); // ... T result; memcpy( &result, &*v.begin() + index * sizeof result, sizeof result ); return result; }
offensichtlich müssen wir uns hier dann keine Gedanken über den Speicherverbrauch machen. Nur dann, wenn dieser vector besonders oft gelesen werden muss, sollte man sich Gedanken machen, wie die Werte direkt aus gelesen werden können (also eine Zugriffsfunktion, die Referenzen auf die Daten liefert). Das ist mit vector mit einem Trick möglich (das Hauptproblem ist aliasing), allerdings ist es dann möglicherweise sinnvoll, einen eigenen einfachen Container zu schreiben - wenn die Daten nur ein einziges mal initialisiert werden, ist sogar ein vector schon etwas überdimensioniert. Wenn Interesse besteht, kann ich das Vorgehen auch noch skizzieren.
-
Nexus schrieb:
Du meinst also eine Art Wrapper, der Container automatisch uminterpretiert?
ja, im wesentlichen schon. der kriegt einen std::vector<char> als eingabe und als templateargument den typ, in den dessen daten reinterpretiert werden sollen. viel mehr als size(), begin() und end() muss er auch gar nicht können.
ich weiß bloß nicht genau, was man dafür alles beachten muss.Oder halt eine Funktion, die ein Element bestimmt interpretiert; kommt halt drauf an, wie häufig du uminterpretiert lesen willst.
alle elemente des blocks haben das gleiche format, das während der gesamten lebensdauer auch gleich bleibt. es muss auch nichts "interpretiert" im herkömmlichen sinn werden, denn die daten liegen schon in finaler, benutzbarer form vor. im grunde will ich ja lediglich zur laufzeit einen passenden iterator erzeugen können.
Nexus schrieb:
Aber ist dir der
std::vector
bereits fix gegeben?nein, ist nicht fix vorgegeben.
std::vector<u8>
war einfach der kleinste gemeinsame nenner und mir ist bisher nichts besseres eingefallen.
-
camper schrieb:
In Abhängigkeit davon, wieviel eigenen Code du schreiben willst, gibt es verschiedene Möglichkeiten. Die einfachste Variante besteht darin, einfache get-Funktionen zu schreiben, die die Daten nur bei Bedarf entsprechend kopieren:
der client soll die gesamten daten fertig interpretiert abrufen können.
std::vector<float> data = foo.get_data(); // perfekt, aber wahrscheinlich nicht machbar std::vector<float> data = foo.get_data<float>(); // auch noch gut std::vector<float> data; foo.get_data(data); // akzeptabel
eins von denen hätte ich am ende gern als schnittstelle, "bei bedarf" hieße also immer komplett.
Das ist mit vector mit einem Trick möglich (das Hauptproblem ist aliasing), allerdings ist es dann möglicherweise sinnvoll, einen eigenen einfachen Container zu schreiben - wenn die Daten nur ein einziges mal initialisiert werden, ist sogar ein vector schon etwas überdimensioniert. Wenn Interesse besteht, kann ich das Vorgehen auch noch skizzieren.
daran wäre ich sehr interessiert.
-
hm, die schnittstellen-beispiele kopieren natürlich die daten auch wieder, wie sinnlos.
std::vector<float> const& data = foo.get_data<float>();
so wars eigentlich gedacht.
-
Hier wäre mal ein Vorschlag für eine Wrapper-Klasse:
// Klassentemplate für "umgewandelte" Vektoren template <typename New, typename Old = float> // evtl. Standardparameter class InterpreterWrapper { public: InterpreterWrapper(std::vector<Old>& UnderlyingVector); New& operator[] (unsigned int Index); private: std::vector<Old>& myVector; }; // Konstruktor, wird mit Referenz auf Vektor initialisiert template <typename New, typename Old> InterpreterWrapper<New, Old>::InterpreterWrapper(std::vector<Old> &UnderlyingVector) : myVector(UnderlyingVector) { } // Operator [] für Random Access mit direkter Uminterpretierung template <typename New, typename Old> New& InterpreterWrapper<New, Old>::operator[] (unsigned int Index) { return *reinterpret_cast<New*>(&myVector[Index]); }
Du kannst da natürlich noch beliebig Funktionen hinzufügen. Ich hab mal den Templateparameter
Old
als Standardparameter angegeben, kommt halt drauf an, was man braucht. Wenn man nur Lesezugriff benötigt, könnte man auch alles über Const-Referenzen statt normaler Referenzen erledigen.
-
Hi @all,
Nexus schrieb:
// Operator [] für Random Access mit direkter Uminterpretierung template <typename New, typename Old> New& InterpreterWrapper<New, Old>::operator[] (unsigned int Index) { return *reinterpret_cast<New*>(&myVector[Index]); }
Die Idee mit der Wrapper-Klasse find ich erstmal prima,
allerdings befürchte ich / bin mir relativ sicher, dass der reinterpret_cast, bei Vererbung
in die Hose geht. Nur als Anmerkung...Gruß,
CSpille
-
hallo,
danke nexus für den beispielcode. ich habe mir jetzt folgendes gebastelt:template <typename DEST, typename SRC = char> class reinterpret_vector { public: typedef DEST dest_type; typedef SRC src_type; typedef const dest_type* iterator; explicit reinterpret_vector(const std::vector<src_type>& data) : m_src(data) { } inline iterator begin() const { return &operator[](0); } inline iterator end() const { return &operator[](size()); } inline std::size_t size() const { if(size_factor == 0) return m_src.size() / size_divisor; else return m_src.size() * size_factor; } inline const dest_type& operator[](unsigned int index) const { return reinterpret_cast<const dest_type*>(&m_src[0])[index]; } private: const std::vector<src_type>& m_src; enum { size_divisor = (sizeof(dest_type) / sizeof(src_type)), size_factor = (sizeof(src_type) / sizeof(dest_type)) }; reinterpret_vector(const reinterpret_vector&); const reinterpret_vector& operator=(const reinterpret_vector&); }; // benutzen: std::vector<char> data(16, 0xba); reinterpret_vector<int> int_data(data); std::cout << std::hex << int_data[0] << std::endl; // 0xbabababa
CSpille schrieb:
allerdings befürchte ich / bin mir relativ sicher, dass der reinterpret_cast, bei Vererbung
in die Hose geht.danke für den hinweis. das ist für meinen einsatzzweck kein problem, weil die daten garantiert nur einfache typen sind.
-
victor schrieb:
hallo,
danke nexus für den beispielcode. ich habe mir jetzt folgendes gebastelt:Gut, dass du den Code brauchen konntest.
Bei dir ist
inline
eigentlich nicht notwendig, denn innerhalb der Klassendefinition definierte Funktionen sind automatisch inline. Abgesehen davon hat man auch durch das explizite Schreiben des Schlüsselwortes nicht viel Einfluss, da das Inlining meistens vom Compiler optimiert/bestimmt wird.CSpille schrieb:
allerdings befürchte ich / bin mir relativ sicher, dass der reinterpret_cast, bei Vererbung
in die Hose geht. Nur als Anmerkung...Was ist denn daran nicht sicher?
reinterpret_cast
versucht doch einfach, den Speicherbereich neu zu interpretieren. Klar macht es nicht sehr viel Sinn, aber was ist das Gefährliche daran?
-
Nexus schrieb:
CSpille schrieb:
allerdings befürchte ich / bin mir relativ sicher, dass der reinterpret_cast, bei Vererbung
in die Hose geht. Nur als Anmerkung...Was ist denn daran nicht sicher?
reinterpret_cast
versucht doch einfach, den Speicherbereich neu zu interpretieren. Klar macht es nicht sehr viel Sinn, aber was ist das Gefährliche daran?reinterpret_cast interpretiert niemals irgendwelche Speicherbereiche - daher kann reinterpret_cast (als Zeiger- oder Referenzcast) auch nie fehlschlagen, solange die Alignmentvoraussetzungen (5.2.10/7) erfüllt sind (das ist unproblematisch bei vector<char>, da dieser die globale Allokationsfunktion operator new benutzt, die stets hinreichend ausgerichteten Speicher liefert). Problematisch ist der folgende Zugriff auf den Speicher, dieser ist bis auf wenige Ausnahmen (3.10/15) immer undefiniert - das es oft genug trotzdem funktioniert ist dummen Compilern zu verdanken, denn 3.10/15 ist primär eine Optimierungsregel (in Analogie zum Schlüsselwort restrict in C, wärend sich restrict um Zugriffspfade auf Objekte gleichen Typs kümmert, geht es bei 3.10/15 um Zugriffe auf Objekte mit einem anderen Typ). Eine ausfürlichere Erklärung plus den Code gibt es später.
-
camper schrieb:
...
Guckst Du sowas eigentlich nach, oder weisst Du das (inkl. Paragraphen) aus dem Kopf?
-
camper schrieb:
Eine ausfürlichere Erklärung plus den Code gibt es später.
du machst es ja ganz schön spannend!
-
camper schrieb:
reinterpret_cast interpretiert niemals irgendwelche Speicherbereiche - daher kann reinterpret_cast (als Zeiger- oder Referenzcast) auch nie fehlschlagen, solange die Alignmentvoraussetzungen (5.2.10/7) erfüllt sind.
Was tut
reinterpret_cast
dann, wenn es nicht einfach versucht, das Bitmuster neu zu interpretieren?