Czy std :: vector kopiuje obiekty z push_back?

169

Po wielu badaniach z valgrind doszedłem do wniosku, że std :: vector tworzy kopię obiektu, który chcesz push_back.

Czy to prawda? Wektor nie może zachować odniesienia lub wskaźnika do obiektu bez kopii?!

Dzięki

benlaug
źródło
20
To jest podstawowa zasada C ++. Obiekty są wartościami. Cesja tworzy kopię. Dwie zmienne odwołujące się do tego samego obiektu nie są możliwe, chyba że zmodyfikujesz typ za pomocą *lub, &aby utworzyć wskaźnik lub odniesienie.
Daniel Earwicker,
8
@DanielEarwicker push_back faktycznie pobiera odniesienie. Z samego podpisu nie wynika jasno, że utworzy kopię.
Brian Gordon,
3
@BrianGordon - Nie mówię, że tak jest! Stąd potrzeba przewodniej zasady. Mimo to możemy wywnioskować coś z podpisu push_back: zajmuje const&. Albo odrzuca wartość (bezużyteczna), albo istnieje metoda pobierania. Patrzymy więc na podpis backi zwraca on zwykły &, więc albo oryginalna wartość została skopiowana, albo constzostała po cichu odrzucona (bardzo źle: potencjalnie niezdefiniowane zachowanie). Zakładając więc, że projektanci vectorbyli racjonalni ( vector<bool>nie sprzeciwiali się), dochodzimy do wniosku, że robi kopie.
Daniel Earwicker,

Odpowiedzi:

183

Tak, std::vector<T>::push_back()tworzy kopię argumentu i przechowuje ją w wektorze. Jeśli chcesz przechowywać wskaźniki do obiektów w swoim wektorze, utwórz plik std::vector<whatever*>zamiast std::vector<whatever>.

Musisz jednak upewnić się, że obiekty, do których odwołują się wskaźniki, pozostają prawidłowe, podczas gdy wektor zawiera odniesienie do nich (inteligentne wskaźniki wykorzystujące idiom RAII rozwiązują problem).

Alexander Gessler
źródło
Chciałbym również zauważyć, że jeśli używasz surowych wskaźników, jesteś teraz odpowiedzialny za ich czyszczenie. Nie ma dobrego powodu, aby to robić (zresztą nie taki, który mi przychodzi do głowy), zawsze powinieneś używać inteligentnego wskaźnika.
Ed S.
1
to powiedziawszy, nie powinieneś używać std :: auto_ptr w kontenerach stl, aby uzyskać więcej informacji: Why-is-it-wrong-to-use-stdauto-ptr-with-standard-container
OriginalCliche
24
Od C ++ 11 push_backwykona ruch zamiast kopii, jeśli argument jest odwołaniem do wartości r. (Obiekty można konwertować na referencje rvalue za pomocą std::move().)
emlai
2
@tuple_cat Twój komentarz powinien brzmieć „jeśli argument jest wartością r”. (Jeśli argumentem jest nazwa jednostki zadeklarowanej jako odniesienie do wartości r, to argument jest w rzeczywistości lwartością i nie zostanie przeniesiony z) - sprawdź moją edycję odpowiedzi „Karl Nicoll”, który popełnił ten błąd na początku
MM
Poniżej jest odpowiedź, ale dla jasności: od C ++ 11 używaj również, emplace_backaby uniknąć kopiowania lub przenoszenia (konstruuj obiekt w miejscu dostarczonym przez kontener).
Wormer,
34

Tak, std::vectorprzechowuje kopie. Skąd powinieneś vectorwiedzieć, jakie są przewidywane czasy życia twoich obiektów?

Jeśli chcesz przenieść lub udostępnić własność obiektów, użyj wskaźników, być może inteligentnych wskaźników, takich jak shared_ptr(znalezione w Boost lub TR1 ), aby ułatwić zarządzanie zasobami.

Georg Fritzsche
źródło
3
naucz się używać shared_ptr - robią dokładnie to, co chcesz. Moim ulubionym idiomem jest typedef boost :: shared_ptr <Foo> FooPtr; Następnie stwórz kontenery FooPtrs
pm100
3
@ pm100 - Czy wiesz boost::ptr_vector?
Manuel
2
Lubię też class Foo { typedef boost::shared_ptr<Foo> ptr; };po prostu pisać Foo::ptr.
Rupert Jones
2
@ pm100 - shared_ptrnie jest dokładnie ogień i zapomnij. Zobacz stackoverflow.com/questions/327573 i stackoverflow.com/questions/701456
Daniel Earwicker,
2
shared_ptr jest dobre, jeśli masz współwłasność, ale jest ogólnie nadużywane. unique_ptr lub boost scoped_ptr mają znacznie większy sens, gdy własność jest jasna.
Nemanja Trifunovic
28

Od c ++ 11 roku, wszystkie standardowe pojemniki ( std::vector, std::mapitp) semantyka wsparcie poruszać, co oznacza, że można teraz przejść rvalues do standardowych pojemników i uniknąć kopię:

// 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");

Alternatywnie możesz użyć różnych inteligentnych wskaźników, aby uzyskać w większości ten sam efekt:

std::unique_ptr przykład

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 przykład

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
źródło
2
Zauważ, że std::make_uniquejest (irytująco) dostępny tylko w C ++ 14 i nowszych. Upewnij się, że powiesz kompilatorowi, aby odpowiednio ustawił zgodność ze standardem, jeśli chcesz skompilować te przykłady.
Laryx Decidua
W 5a możesz użyć, auto pFoo =aby uniknąć powtórzeń; i wszystkie std::stringrzutowania można usunąć (istnieje niejawna konwersja z literałów łańcuchowych do std::string)
MM
2
@ user465139 make_uniquemożna łatwo zaimplementować w C ++ 11, więc jest to tylko drobna irytacja dla kogoś, kto utknął w kompilatorze C ++ 11
MM
1
@MM: Rzeczywiście. Oto podręcznikowa implementacja:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua
1
@Anakin - Tak, powinni to zrobić, ale tylko wtedy, gdy kopiujesz. Jeśli używasz std::move()with std::shared_ptr, oryginalny wskaźnik współdzielony może mieć zmieniony wskaźnik, ponieważ własność została przekazana do wektora. Zobacz tutaj: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Karl Nicoll,
15

std :: vector zawsze tworzy kopię tego, co jest przechowywane w wektorze.

Jeśli zachowujesz wektor wskaźników, utworzy on kopię wskaźnika, ale nie instancji, na którą wskazuje wskaźnik. Jeśli masz do czynienia z dużymi obiektami, możesz (i prawdopodobnie powinieneś) zawsze używać wektora wskaźników. Często użycie wektora inteligentnych wskaźników odpowiedniego typu jest dobre ze względów bezpieczeństwa, ponieważ w przeciwnym razie obsługa czasu życia obiektu i zarządzanie pamięcią może być trudne.

Reed Copsey
źródło
3
nie zależy od typu. Zawsze tworzy kopię. Jeśli jego wskaźnik jest kopią wskaźnika
pm100
Oboje macie rację. Technicznie tak, zawsze tworzy kopię. Praktycznie, jeśli przekażesz mu wskaźnik do obiektu, skopiuje on wskaźnik, a nie obiekt. Powinieneś bezpiecznie używać odpowiedniego inteligentnego wskaźnika.
Steven Sudit
1
Tak, to zawsze kopiuje - jednak „obiekt”, do którego odnosi się OP, jest najprawdopodobniej klasą lub strukturą, więc odnosiłem się do tego, czy kopiowanie „obiektu” zależy od definicji. Jednak źle sformułowane.
Reed Copsey
3

Nie tylko std :: vector tworzy kopię tego, co odpychasz, ale definicja zbioru mówi, że to zrobi i że nie możesz używać obiektów bez poprawnej semantyki kopiowania w wektorze. Na przykład nie używasz auto_ptr w wektorze.

Liz Albin
źródło
2

W C ++ 11 istotna jest emplacerodzina funkcji składowych, które umożliwiają przenoszenie własności obiektów poprzez przenoszenie ich do kontenerów.

Tak wyglądałby idiom użycia

std::vector<Object> objs;

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

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

Przeniesienie obiektu lvalue jest ważne, ponieważ w przeciwnym razie zostałby przekazany jako referencja lub odwołanie do stałej, a konstruktor przenoszenia nie zostałby wywołany.

LemonPi
źródło
0

jeśli nie chcesz kopii; wtedy najlepszym sposobem jest użycie wektora wskaźnikowego (lub innej struktury, która służy temu samemu celowi). jeśli chcesz kopie; użyj bezpośrednio push_back (). nie masz innego wyboru.

rahmivolkan
źródło
1
Uwaga dotycząca wektorów wskaźnikowych: vector <shared_ptr <obj>> jest dużo bezpieczniejszy niż vector <obj *>, a shared_ptr jest częścią standardu z zeszłego roku.
bogaty. E
-1

Dlaczego zajęło nam to dużo badań w Valgrind, żeby się tego dowiedzieć! Po prostu udowodnij to sobie prostym kodem, np

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

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

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

Jeśli drukowany jest tekst „hello world”, obiekt musi zostać skopiowany

NexusSquared
źródło
4
To nie stanowi dowodu. Gdyby obiekt nie został skopiowany, ostatnia instrukcja byłaby niezdefiniowanym zachowaniem i mogłaby wydrukować hello.
Mat
4
poprawnym testem byłaby modyfikacja jednego z dwóch po wstawieniu. Gdyby były tym samym obiektem (gdyby wektor przechowywał odniesienie), oba zostałyby zmodyfikowane.
Francesco Dondi,