std::vector<T> als std::vector<U> interpretieren



  • 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?


  • Mod

    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?


Anmelden zum Antworten