Чи std :: вектор копіює об'єкти push_back?


169

Після багатьох досліджень з valgrind, я зробив висновок, що std :: vector робить копію об'єкта, який ви хочете push_back.

Це справді правда? Вектор не може зберігати посилання чи покажчик об’єкта без копії ?!

Дякую


20
Це основний принцип C ++. Об'єкти - значення. Призначення робить копію. Дві змінні , що відносяться до одного об'єкту не представляється можливим , якщо ви не зміните тип з *або &зробити покажчик або посилання.
Даніель Ервікер

8
@DanielEarwicker push_back насправді бере посилання. З підпису не зрозуміло, що він зробить копію.
Брайан Гордон

3
@BrianGordon - Не кажучи, що так! Звідси необхідність керівного принципу. Тим не менш , ми можемо зробити висновок , що - то з підпису push_back: вона займає const&. Або викидає значення (марно), або існує метод пошуку. Таким чином, ми дивимось на підпис back, і він повертається просто &, тому або оригінальне значення було скопійовано, або constтихе відкинуте (дуже погано: потенційно невизначена поведінка). Тож припускаючи, що дизайнери vectorбули раціональними ( vector<bool>не протистоячими), ми робимо висновок, що це робить копії.
Даніель Ервікер

Відповіді:


183

Так, std::vector<T>::push_back()створюється копія аргументу та зберігається у векторі. Якщо ви хочете зберігати покажчики на об’єкти у вашому векторі, створіть std::vector<whatever*>замість цього std::vector<whatever>.

Однак вам потрібно переконатися, що об'єкти, на які посилаються вказівники, залишаються дійсними, поки вектор містить посилання на них (розумні покажчики, що використовують ідіому RAII, вирішують проблему).


Я також зауважу, що якщо ви користуєтесь сирими вказівниками, ви зараз відповідальні за прибирання після них. Немає вагомих причин для цього (ні про кого я не міг подумати), завжди слід використовувати розумний вказівник.
Ред С.

1
з цього приводу, ви не повинні використовувати std :: auto_ptr в контейнерах stl, для отримання додаткової інформації: чому-це-неправильно-використовувати-stdauto-ptr-with-standard-container
OriginalCliche

24
Оскільки C ++ 11 push_backбуде виконувати переміщення замість копії, якщо аргумент є посиланням на rvalue. (Об'єкти можна перетворити на посилання на рецензування std::move().)
emlai

2
@tuple_cat ваш коментар повинен сказати "якщо аргумент є оцінкою". (Якщо аргумент - це ім'я суб'єкта, оголошеного посиланням на rvalue, тоді аргумент насправді є lvalue і з нього не буде переміщено) - перевірте мою редакцію у відповіді "Карла Ніколя", який спочатку допустив цю помилку
MM

Нижче є відповідь, але для того, щоб було зрозуміло: Оскільки C ++ 11 також використовується, emplace_backщоб уникнути копіювання чи переміщення (побудуйте об'єкт на місці, наданому контейнером).
гірше

34

Так, std::vectorзберігає копії. Як слід vectorзнати, який очікуваний час життя ваших об’єктів?

Якщо ви хочете передати або поділити право власності на об'єкти, використовуйте вказівники, можливо, розумні вказівники, як-от shared_ptr(знайдено в Boost або TR1 ), щоб полегшити управління ресурсами.


3
навчитися використовувати shared_ptr - вони роблять саме те, що ти хочеш. Моя улюблена ідіома - це typedef boost :: shared_ptr <Foo> FooPtr; Потім зробіть контейнери FooPtrs
pm100

3
@ pm100 - Ви знаєте boost::ptr_vector?
Мануель

2
Мені також подобається class Foo { typedef boost::shared_ptr<Foo> ptr; };просто писати Foo::ptr.
Руперт Джонс

2
@ pm100 - shared_ptrце не зовсім вогонь і забудь. Дивіться stackoverflow.com/questions/327573 та stackoverflow.com/questions/701456
Daniel Earwicker

2
shared_ptr добре, якщо ви маєте спільне право власності, але, як правило, це занадто багато. unique_ptr або boost scoped_ptr мають набагато більше сенсу, коли право власності зрозуміло.
Неманя Трифунович

28

З C ++ 11 далі всі стандартні контейнери ( std::vector, std::mapі т.д.) підтримують семантику переміщення, тобто тепер ви можете передавати rvalues ​​у стандартні контейнери та уникати копії:

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

Крім того, ви можете використовувати різні розумні покажчики, щоб отримати переважно однаковий ефект:

std::unique_ptr приклад

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 приклад

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.

2
Зауважте, що std::make_uniqueце (прикро) доступне лише на C ++ 14 і вище. Переконайтесь, що ви скажете своєму компілятору встановити його стандартну відповідність відповідно, якщо ви хочете скласти ці приклади.
Laryx Decidua

5a ви можете використовувати, auto pFoo =щоб уникнути повторення; і всі std::stringкасти можуть бути видалені (є неявна конверсія з рядкових літералів в std::string)
ММ

2
@ user465139 make_uniqueлегко реалізувати в C ++ 11, тому для когось, хто застряв у компіляторі C ++ 11, це лише незначне роздратування
ММ

1
@MM: Дійсно. Ось реалізація підручника:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua

1
@Anakin - Так, вони повинні робити, але тільки якщо ви копіюєте. Якщо ви користуєтесь std::move()з std::shared_ptr, оригінальний загальний покажчик може змінити його вказівник з моменту передачі права власності на вектор. Дивіться тут: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Карл Ніколь

15

std :: вектор завжди робить копію того, що зберігається у векторі.

Якщо ви зберігаєте вектор покажчиків, то він зробить копію вказівника, але не той екземпляр, на який вказує вказівник. Якщо ви маєте справу з великими об'єктами, ви завжди можете (і, мабуть, повинні) використовувати вектор вказівників. Часто використання векторів розумних покажчиків відповідного типу корисно з метою безпеки, оскільки поводження з об'єктом життя та управління пам'яттю може бути складним інакше.


3
це не залежить від типу. Це завжди робить копію. Якщо його вказівник робить копію вказівника
pm100

Ви обоє праві. Технічно так, це завжди робить копію. Практично, якщо ви передаєте йому вказівник на об’єкт, він копіює вказівник, а не об’єкт. Безпечно, ви повинні використовувати відповідний розумний вказівник.
Стівен Судіт

1
Так, це завжди копіювання - Однак, "об'єкт", на який посилається ОП, швидше за все, це клас або структура, тому я мав на увазі, чи це копіювання "Об'єкта", залежить від визначення. Хоча погано сформульовано.
Рід Копсей

3

Std :: vector не тільки робить копію всього, що ви відштовхуєте, але визначення колекції визначає, що це буде робити, і що ви не можете використовувати об'єкти без правильної семантики копіювання у векторному. Так, наприклад, ви не використовуєте auto_ptr у векторі.


2

У C ++ 11 є актуальною emplaceсімейство функцій-членів, які дозволяють перенести право власності на об'єкти шляхом переміщення їх у контейнери.

Ідіома вживання виглядала б так

std::vector<Object> objs;

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

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

Переміщення об'єкта lvalue важливо, оскільки в іншому випадку воно буде передаватися як посилання або посилання const, і конструктор переміщення не буде викликатися.


0

якщо ви хочете не копії; тоді найкращим способом є використання вказівного вектора (або іншої структури, яка служить для тієї ж мети). якщо ви хочете копії; використовувати безпосередньо push_back (). у вас немає іншого вибору.


1
Примітка про вектори вказівника: вектор <shared_ptr <obj>> набагато безпечніше, ніж вектор <obj *>, а shared_ptr є частиною стандарту станом на минулий рік.
багатий.e

-1

Чому знадобилося чимало валліндських розслідувань, щоб дізнатися це! Просто доведіть це самим простим кодом, наприклад

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

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

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

Якщо надруковано "привіт світ", об'єкт повинен бути скопійований


4
Це не є доказом. Якщо об'єкт не був скопійований, останнє твердження буде не визначеним поведінкою і може надрукувати привіт.
Мат

4
правильним тестом буде модифікація одного з двох після вставки. Якби вони були одним і тим же об'єктом (якщо вектор зберігав посилання), обидва будуть модифіковані.
Франческо Донді
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.