2014-11-26 14 views
7

Ich benutze eine externe Bibliothek, die auf große Datenmengen arbeitet. Die Daten werden von einem rohen Zeiger plus der Länge übergeben. Die Bibliothek beansprucht nicht den Besitz des Zeigers, sondern ruft eine bereitgestellte Rückruffunktion (mit denselben zwei Argumenten) auf, wenn sie mit den Daten fertig ist.Übernahme von Speicher von Std :: Vektor

Die Daten werden bequem mit std::vector<T> vorbereitet, und ich möchte lieber nicht auf diesen Komfort verzichten. Das Kopieren der Daten kommt nicht in Frage. Daher muss ich den Speicherpuffer, der einem std::vector<T> gehört, "übernehmen" und (später) die Zuordnung im Rückruf rückgängig machen.

Meine aktuelle Lösung sieht wie folgt aus:

std::vector<T> input = prepare_input(); 
T * data = input.data(); 
size_t size = input.size(); 
// move the vector to "raw" storage, to prevent deallocation 
alignas(std::vector<T>) char temp[sizeof(std::vector<T>)]; 
new (temp) std::vector<T>(std::move(input)); 
// invoke the library 
lib::startProcesing(data, size); 

und in der Callback-Funktion:

void callback(T * data, size_t size) { 
    std::allocator<T>().deallocate(data, size); 
} 

Diese Lösung funktioniert, weil der Standard der allocator deallocate Funktion zweites Argument ignoriert (die Elementzählwert) und ruft einfach ::operator delete(data). Wenn dies nicht der Fall ist, können schlimme Dinge passieren, da der size des Eingabevektors ein wenig kleiner sein kann als sein capacity.

Meine Frage ist: gibt es eine zuverlässige (wrt. Der C++ Standard) Weise, den Puffer von std::vector zu übernehmen und es "manuell" zu einem späteren Zeitpunkt freizugeben?

+1

Sie werden über den gesamten Vektor zu nehmen. –

+0

Wäre nett, wenn 'vector' eine' detach' Funktion hätte ... aber es ist nicht –

+0

@TC: aber ich habe keinen Platz es zu speichern - die Eingabe Produktion und Freigabe erfolgt in zwei separaten Teilen des Programms –

Antwort

2

Sie können den Speicher nicht von einem Vektor übernehmen, aber Sie können das zugrunde liegende Problem auf andere Weise lösen.

Hier ist, wie ich es annähme - es ist ein bisschen hacky wegen der statischen globalen Variable und nicht threadsicher, aber es kann so mit einigen einfachen Sperren um Zugriffe auf das registry Objekt gemacht werden.

static std::map<T*, std::vector<T>*> registry; 
void my_startProcessing(std::vector<T> * data) { 
    registry.put(data->data(), data); 
    lib::startProcesing(data->data(), data->size()); 
} 

void my_callback(T * data, size_t length) { 
    std::vector<T> * original = registry.get(data); 
    delete original; 
    registry.remove(data); 
} 

Jetzt können Sie tun nur

std::vector<T> * input = ... 
my_startProcessing(input); 

Aber Vorsicht! Schlechte Dinge passieren, wenn Sie Elemente hinzufügen/entfernen, nachdem Sie my_startProcessing aufgerufen haben - der Puffer, den die Bibliothek hat, kann ungültig sein. (Sie können Werte zu ändern, in dem Vektor erlaubt sein, wie ich glaube, dass korrekt durch die Daten schreiben wird, aber das hängt davon ab, was die Bibliothek zu können.)

Auch funktioniert das nicht, wenn T = bool seit std::vector<bool>::data() funktioniert nicht.

+0

Sieht gut aus. Wenn ich keine Möglichkeit finde, globale Variablen zu vermeiden, werde ich das mit ein bisschen 'std :: mutex' und' std :: unique_ptr' streuen und es sollte in Ordnung sein. Vielen Dank! –

1

Sie könnten benutzerdefinierte Klasse erstellen über einen Vektor erstellen.

Hauptpunkt hier ist die Verwendung der Bewegungssemantik in SomeData Konstruktor.

  • Sie bekommen vorbereitet Daten, ohne zu kopieren (beachten Sie, dass Quellenvektor gelöscht werden)
  • Daten korrekt durch thisData Vektor destructor angeordnet werden
  • Quellenvektor kann ohne Problem
entsorgt werden

Da der zugrunde liegende Datentyp Array sein soll, können Sie den Startzeiger und eine Datengröße berechnen (siehe unten SomeDataImpl.h):

SomeData.h

#pragma once 
#include <vector> 

template<typename T> 
class SomeData 
{ 
    std::vector<T> thisData; 

public: 
    SomeData(std::vector<T> && other); 

    const T* Start() const; 
    size_t Size() const; 
}; 

#include "SomeDataImpl.h" 

SomeDataImpl.h

#pragma once 

template<typename T> 
SomeData<T>::SomeData(std::vector<T> && otherData) : thisData(std::move(otherData)) { } 

template<typename T> 
const T* SomeData<T>::Start() const { 
    return thisData.data(); 
} 

template<typename T> 
size_t SomeData<T>::Size() const { 
    return sizeof(T) * thisData.size(); 
} 

Anwendungsbeispiel:

#include <iostream> 
#include "SomeData.h" 

template<typename T> 
void Print(const T * start, size_t size) { 
    size_t toPrint = size/sizeof(T); 
    size_t printed = 0; 

    while(printed < toPrint) { 
     std::cout << *(start + printed) << ", " << start + printed << std::endl; 
     ++printed; 
    } 
} 

int main() { 
    std::vector<int> ints; 
    ints.push_back(1); 
    ints.push_back(2); 
    ints.push_back(3); 

    SomeData<int> someData(std::move(ints)); 
    Print<int>(someData.Start(), someData.Size()); 

    return 0; 
}