Чи 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.