Wie initialisiere ich std :: vector aus einem Array im C-Stil?

174

Was ist der billigste Weg, um ein std::vectorArray aus einem C-Stil zu initialisieren ?

Beispiel: In der folgenden Klasse habe ich eine vector, aber aufgrund externer Einschränkungen werden die Daten als Array im C-Stil übergeben:

class Foo {
  std::vector<double> w_;
public:
  void set_data(double* w, int len){
   // how to cheaply initialize the std::vector?
}

Natürlich kann ich w_.resize()die Elemente aufrufen und dann durchlaufen oder aufrufen std::copy(). Gibt es bessere Methoden?

Frank
quelle
11
Der Kern des Problems besteht darin, dass der Vektor nicht wissen kann, ob derselbe Allokator zum Erstellen Ihres C-Array verwendet wurde. Als solches muss der Vektor Speicher unter Verwendung seines eigenen Allokators zuweisen. Andernfalls könnte das zugrunde liegende Array einfach ausgetauscht und durch Ihr Array ersetzt werden.
Nichtig

Antworten:

233

Vergessen Sie nicht, dass Sie Zeiger als Iteratoren behandeln können:

w_.assign(w, w + len);
Pavel Minaev
quelle
3
Es ist ein Problem der Qualität der Implementierung. Da Iteratoren Tags haben, die ihre Kategorien angeben, kann eine Implementierung von sie assignsicherlich zur Optimierung verwenden. Zumindest in VC ++ macht es genau das.
Pavel Minaev
38
Die schnelle Lösung könnte std :: vector <double> w_ (w, w + len) sein;
Jamk
1
Dadurch werden Elemente in einen neu erstellten Speicher für 'w_' kopiert. 'w_.data' zeigt nicht auf 'w'. Sie müssen immer noch 'w' freigeben. Es gibt keine Eigentumsübertragung
Indy9000
1
Wenn es sich um ein Element nach dem Ende handelt, sollte es in Ordnung sein (genau wie v.end()ein Iterator, der in einem ähnlichen Fall eines nach dem Ende mit einem Vektor zeigt). Wenn Sie eine Behauptung erhalten, ist etwas anderswo nicht in Ordnung.
Pavel Minaev
1
Wird der Array-Speicher nur schnell freigegeben, wenn der Vektor den Gültigkeitsbereich verlässt?
Adrian Koch
41

Sie verwenden das Wort "Initialisieren", sodass unklar ist, ob es sich um eine einmalige Zuweisung handelt oder ob sie mehrmals auftreten kann.

Wenn Sie nur eine einmalige Initialisierung benötigen, können Sie diese in den Konstruktor einfügen und den Konstruktor mit zwei Iteratorvektoren verwenden:

Foo::Foo(double* w, int len) : w_(w, w + len) { }

Verwenden Sie andernfalls zuweisen wie zuvor vorgeschlagen:

void set_data(double* w, int len)
{
    w_.assign(w, w + len);
}
Mark B.
quelle
1
In meinem Fall erfolgt die Zuordnung wiederholt.
Frank
12

Sie können die Größe des Arrays automatisch "lernen":

template<typename T, size_t N>
void set_data(const T (&w)[N]){
    w_.assign(w, w+N);
}

Hoffentlich können Sie die Schnittstelle wie oben in set_data ändern. Es akzeptiert weiterhin ein Array im C-Stil als erstes Argument. Es nimmt es einfach als Referenz.


Wie es funktioniert

[Update: Sehen Sie hier für eine umfassendere Diskussion über die Größe Lernen]

Hier ist eine allgemeinere Lösung:

template<typename T, size_t N>
void copy_from_array(vector<T> &target_vector, const T (&source_array)[N]) {
    target_vector.assign(source_array, source_array+N);
}

Dies funktioniert, weil das Array als Referenz auf ein Array übergeben wird. In C / C ++ können Sie ein Array nicht als Funktion übergeben, sondern es zerfällt in einen Zeiger und Sie verlieren die Größe. In C ++ können Sie jedoch einen Verweis auf das Array übergeben.

Um ein Array als Referenz zu übergeben, müssen die Typen genau übereinstimmen. Die Größe eines Arrays ist Teil seines Typs. Dies bedeutet, dass wir den Vorlagenparameter N verwenden können, um die Größe für uns zu lernen.

Es könnte noch einfacher sein, diese Funktion zu haben, die einen Vektor zurückgibt. Bei entsprechenden Compiler-Optimierungen sollte dies schneller sein, als es aussieht.

template<typename T, size_t N>
vector<T> convert_array_to_vector(const T (&source_array)[N]) {
    return vector<T>(source_array, source_array+N);
}
Aaron McDaid
quelle
1
In der letzten Probe return { begin(source_array), end(source_array) };ist auch möglich
MM
12

Nun, Pavel war nah dran, aber es gibt noch eine einfachere und elegantere Lösung, um einen sequentiellen Container aus einem Array im AC-Stil zu initialisieren.

In deinem Fall:

w_ (array, std::end(array))
  • Array gibt uns einen Zeiger auf den Anfang des Arrays (hat seinen Namen nicht verstanden),
  • Mit std :: end (Array) erhalten wir einen Iterator zum Ende des Arrays.
Mugurel
quelle
1
Was beinhaltet / Version von C ++ erfordert dies?
Vlad
1
Dies ist einer der Konstruktoren von std :: vector ab mindestens c ++ 98 .... Er wird als 'Bereichskonstruktor' bezeichnet. cplusplus.com/reference/vector/vector/vector Probieren Sie es aus.
Mugurel
1
Eine unabhängigere Version ist: w_ (std :: begin (Array), std :: end (Array)); (In Zukunft können Sie ein C-Array für einen C ++ - Container ändern.)
Andrew Romanov
13
Wohlgemerkt, dies funktioniert nur, wenn Sie ein reales arrayArray haben (was normalerweise bedeutet, dass Sie von einem globalen oder lokalen (in der aktuellen Funktion deklarierten) Array kopieren). Im Fall des OP empfängt er einen Zeiger und eine Länge. Da die Länge nicht angegeben ist, können sie nicht zum Erhalt eines Zeigers auf ein Array mit Größe oder etwas anderem wechseln und std::endfunktionieren daher nicht.
ShadowRanger
2
vectorüberlastet nicht operator(), daher wird dies nicht kompiliert. std::endDas Aufrufen eines Zeigers ist ebenfalls sinnlos (in der Frage wird gefragt, ob ein Vektor aus einem Zeiger und einer separaten Längenvariablen zugewiesen werden soll). Es würde Ihre Antwort verbessern, mehr Kontext über das zu zeigen, was Sie vorschlagen möchten
MM
2

Die schnelle generische Antwort:

std::vector<double> vec(carray,carray+carray_size); 

oder fragenspezifisch:

std::vector<double> w_(w,w+len); 

basierend auf oben : Vergessen Sie nicht, dass Sie Zeiger als Iteratoren behandeln können

eusoubrasileiro
quelle
0

std::vector<double>::assignist der richtige Weg, weil es wenig Code ist . Aber wie funktioniert das eigentlich? Wird die Größe nicht geändert und dann kopiert? In der MS-Implementierung von STL verwende ich es genau so.

Ich fürchte, es gibt keinen schnelleren Weg , um Ihre (Neu-) Initialisierung zu implementieren std::vector.

Janusz Lenar
quelle
Was ist, wenn Daten zwischen dem Vektor und einem Array geteilt werden sollen? Müssen wir in diesem Fall etwas kopieren?
Vlad
Ist das eine Antwort oder eine Frage? Was bringt es zu den bereits vorhandenen Antworten?
Jean-François Fabre
@ Jean-FrançoisFabre und was bringt dein Kommentar? ;) stimmt, es ist eine schlechte Antwort, die vor Ewigkeiten gegeben wurde.
Janusz Lenar