Kopiert std :: vector die Objekte mit einem push_back?

168

Nach vielen Untersuchungen mit valgrind bin ich zu dem Schluss gekommen, dass std :: vector eine Kopie eines Objekts erstellt, das Sie push_back möchten.

Stimmt das wirklich ? Ein Vektor kann ohne Referenz keine Referenz oder einen Zeiger eines Objekts behalten ?!

Vielen Dank

Benlaug
quelle
20
Dies ist ein Grundprinzip von C ++. Objekte sind Werte. Zuordnung macht eine Kopie. Zwei Variablen, die sich auf dasselbe Objekt beziehen, sind nur möglich, wenn Sie den Typ mit *oder ändern &, um einen Zeiger oder eine Referenz zu erstellen.
Daniel Earwicker
8
@DanielEarwicker push_back nimmt tatsächlich eine Referenz. Aus der Unterschrift allein geht nicht hervor, dass eine Kopie erstellt wird.
Brian Gordon
3
@BrianGordon - Das heißt nicht! Daher die Notwendigkeit des Leitprinzips. Trotzdem können wir etwas aus der Unterschrift von ableiten push_back: es braucht a const&. Entweder wird der Wert weggeworfen (nutzlos), oder es gibt eine Abrufmethode. Wir sehen uns also die Signatur von backan und sie wird einfach zurückgegeben &, sodass entweder der ursprüngliche Wert kopiert oder der Wert conststillschweigend weggeworfen wurde (sehr schlecht: möglicherweise undefiniertes Verhalten). Unter der Annahme, dass die Designer von vectorrational waren ( vector<bool>nicht widerstehen), schließen wir, dass es Kopien macht.
Daniel Earwicker

Antworten:

183

Ja, std::vector<T>::push_back()erstellt eine Kopie des Arguments und speichert es im Vektor. Wenn Sie Zeiger auf Objekte in Ihrem Vektor speichern möchten, erstellen Sie eine std::vector<whatever*>anstelle von std::vector<whatever>.

Sie müssen jedoch sicherstellen, dass die Objekte, auf die die Zeiger verweisen, gültig bleiben, während der Vektor einen Verweis auf sie enthält (intelligente Zeiger, die die RAII-Sprache verwenden, lösen das Problem).

Alexander Gessler
quelle
Ich würde auch bemerken, dass Sie, wenn Sie rohe Zeiger verwenden, jetzt dafür verantwortlich sind, nach ihnen aufzuräumen. Es gibt keinen guten Grund dafür (ich kann mir sowieso keinen vorstellen), Sie sollten immer einen intelligenten Zeiger verwenden.
Ed S.
1
Das heißt, Sie sollten std :: auto_ptr nicht in stl-Containern verwenden, um weitere Informationen zu erhalten: Warum ist es falsch, stdauto-ptr-mit-Standard-Containern zu verwenden
OriginalCliche
24
push_backFührt seit C ++ 11 eine Verschiebung anstelle einer Kopie durch, wenn das Argument eine rWert-Referenz ist. (Objekte können mit in. Wertreferenzen mit konvertiert werden std::move().)
Emlai
2
@tuple_cat Ihr Kommentar sollte "wenn das Argument ein rWert ist" sagen. (Wenn das Argument der Name einer Entität ist, die als r-Wert-Referenz deklariert wurde, ist das Argument tatsächlich ein l-Wert und wird nicht verschoben.) - Überprüfen Sie meine Bearbeitung auf die Antwort von "Karl Nicoll", die diesen Fehler ursprünglich gemacht hat
MM
Es gibt eine Antwort unten, aber um es klar zu machen: Da C ++ 11 auch verwendet wird emplace_back, um jegliches Kopieren oder Verschieben zu vermeiden (Objekt an Ort und Stelle konstruieren, das vom Container bereitgestellt wird).
Wormer
34

Ja, std::vectorspeichert Kopien. Woher sollten Sie vectordie erwarteten Lebensdauern Ihrer Objekte wissen?

Wenn Sie das Eigentum an den Objekten übertragen oder teilen möchten, verwenden Sie Zeiger, möglicherweise intelligente Zeiger wie shared_ptr(in Boost oder TR1 ), um die Ressourcenverwaltung zu vereinfachen.

Georg Fritzsche
quelle
3
lerne den Umgang mit shared_ptr - sie machen genau das, was du willst. Meine Lieblingssprache ist typedef boost :: shared_ptr <Foo> FooPtr; Dann machen Sie Container von FooPtrs
pm100
3
@ pm100 - Weißt du boost::ptr_vector?
Manuel
2
Ich benutze class Foo { typedef boost::shared_ptr<Foo> ptr; };es auch gerne, um nur zu schreiben Foo::ptr.
Rupert Jones
2
@ pm100 - shared_ptrist nicht gerade Feuer und vergessen. Siehe stackoverflow.com/questions/327573 und stackoverflow.com/questions/701456
Daniel Earwicker
2
shared_ptr ist gut, wenn Sie gemeinsam Eigentümer sind, wird aber im Allgemeinen überbeansprucht. unique_ptr oder boost scoped_ptr sind viel sinnvoller, wenn der Besitz klar ist.
Nemanja Trifunovic
28

Von C ++ 11 ab, alle Standardcontainer ( std::vector, std::mapusw.) convoy Semantik, was bedeutet , dass Sie jetzt rvalues zu Standardcontainer und vermeiden eine Kopie passieren kann:

// Example object class.
class object
{
private:
    int             m_val1;
    std::string     m_val2;

public:
    // Constructor for object class.
    object(int val1, std::string &&val2) :
        m_val1(val1),
        m_val2(std::move(val2))
    {

    }
};

std::vector<object> myList;

// #1 Copy into the vector.
object foo1(1, "foo");
myList.push_back(foo1);

// #2 Move into the vector (no copy).
object foo2(1024, "bar");
myList.push_back(std::move(foo2));

// #3 Move temporary into vector (no copy).
myList.push_back(object(453, "baz"));

// #4 Create instance of object directly inside the vector (no copy, no move).
myList.emplace_back(453, "qux");

Alternativ können Sie verschiedene intelligente Zeiger verwenden, um größtenteils den gleichen Effekt zu erzielen:

std::unique_ptr Beispiel

std::vector<std::unique_ptr<object>> myPtrList;

// #5a unique_ptr can only ever be moved.
auto pFoo = std::make_unique<object>(1, "foo");
myPtrList.push_back(std::move(pFoo));

// #5b unique_ptr can only ever be moved.
myPtrList.push_back(std::make_unique<object>(1, "foo"));

std::shared_ptr Beispiel

std::vector<std::shared_ptr<object>> objectPtrList2;

// #6 shared_ptr can be used to retain a copy of the pointer and update both the vector
// value and the local copy simultaneously.
auto pFooShared = std::make_shared<object>(1, "foo");
objectPtrList2.push_back(pFooShared);
// Pointer to object stored in the vector, but pFooShared is still valid.
Karl Nicoll
quelle
2
Beachten Sie, dass dies std::make_unique(ärgerlicherweise) nur in C ++ 14 und höher verfügbar ist. Stellen Sie sicher, dass Sie Ihren Compiler anweisen, seine Standardkonformität entsprechend festzulegen, wenn Sie diese Beispiele kompilieren möchten.
Laryx Decidua
In 5a können Sie auto pFoo =Wiederholungen vermeiden. und alle std::stringCasts können entfernt werden (es gibt eine implizite Konvertierung von String-Literalen in std::string)
MM
2
@ user465139 make_uniquekann leicht in C ++ 11 implementiert werden, so ist es nur ein kleiner Ärger für jemanden, der mit einem C ++ 11-Compiler feststeckt
MM
1
@ MM: In der Tat. Hier ist Lehrbuch Implementierung:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua
1
@ Anakin - Ja, das sollten sie tun, aber nur, wenn Sie kopieren. Wenn Sie std::move()mit verwenden std::shared_ptr, wird der Zeiger des gemeinsam genutzten ursprünglichen Zeigers möglicherweise geändert, seit der Besitz an den Vektor übergeben wurde. Siehe hier: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Karl Nicoll
15

std :: vector erstellt immer eine Kopie von allem, was im Vektor gespeichert ist.

Wenn Sie einen Zeigervektor beibehalten, wird eine Kopie des Zeigers erstellt, nicht jedoch die Instanz, auf die der Zeiger zeigt. Wenn Sie mit großen Objekten arbeiten, können (und sollten) Sie immer einen Zeigervektor verwenden. Die Verwendung eines Vektors von intelligenten Zeigern eines geeigneten Typs ist häufig aus Sicherheitsgründen gut, da die Handhabung der Objektlebensdauer und der Speicherverwaltung ansonsten schwierig sein kann.

Reed Copsey
quelle
3
es hängt nicht vom Typ ab. Es wird immer eine Kopie erstellt. Wenn es sich um einen Zeiger handelt, wird eine Kopie des Zeigers erstellt
pm100
Sie haben beide recht. Technisch gesehen macht es immer eine Kopie. Wenn Sie ihm einen Zeiger auf das Objekt übergeben, wird praktisch der Zeiger kopiert, nicht das Objekt. Sicherlich sollten Sie einen geeigneten intelligenten Zeiger verwenden.
Steven Sudit
1
Ja, es wird immer kopiert. Das "Objekt", auf das sich das OP bezieht, ist jedoch höchstwahrscheinlich eine Klasse oder Struktur. Daher habe ich mich darauf bezogen, ob das Kopieren des "Objekts" von der Definition abhängt. Schlecht formuliert.
Reed Copsey
3

Std :: vector erstellt nicht nur eine Kopie von allem, was Sie zurückschieben, sondern die Definition der Auflistung besagt auch, dass dies der Fall ist und dass Sie möglicherweise keine Objekte ohne die korrekte Kopiersemantik innerhalb eines Vektors verwenden. So verwenden Sie beispielsweise nicht auto_ptr in einem Vektor.

Liz Albin
quelle
2

Relevant in C ++ 11 ist die emplaceFamilie der Elementfunktionen, mit denen Sie das Eigentum an Objekten übertragen können, indem Sie sie in Container verschieben.

Die Gebrauchssprache würde so aussehen

std::vector<Object> objs;

Object l_value_obj { /* initialize */ };
// use object here...

objs.emplace_back(std::move(l_value_obj));

Die Verschiebung für das lvalue-Objekt ist wichtig, da es sonst als Referenz oder const-Referenz weitergeleitet würde und der Verschiebungskonstruktor nicht aufgerufen würde.

LemonPi
quelle
0

wenn Sie nicht die Kopien wollen; Dann ist es am besten, einen Zeigervektor (oder eine andere Struktur, die für dasselbe Ziel dient) zu verwenden. wenn Sie die Kopien wollen; benutze direkt push_back (). Sie haben keine andere Wahl.

rahmivolkan
quelle
1
Ein Hinweis zu Zeigervektoren: vector <shared_ptr <obj>> ist viel sicherer als vector <obj *> und shared_ptr ist seit letztem Jahr Teil des Standards.
rich.e
-1

Warum waren viele Valgrind-Untersuchungen erforderlich, um dies herauszufinden? Beweisen Sie es sich einfach mit einem einfachen Code, z

std::vector<std::string> vec;

{
      std::string obj("hello world");
      vec.push_pack(obj);
}

std::cout << vec[0] << std::endl;  

Wenn "Hallo Welt" gedruckt wird, muss das Objekt kopiert worden sein

NexusSquared
quelle
4
Dies ist kein Beweis. Wenn das Objekt nicht kopiert würde, wäre Ihre letzte Anweisung ein undefiniertes Verhalten und könnte Hallo drucken.
Mat
4
Der richtige Test wäre, einen der beiden nach dem Einfügen zu ändern. Wenn sie dasselbe Objekt wären (wenn der Vektor eine Referenz speichern würde), würden beide geändert.
Francesco Dondi