Apakah std :: vector menyalin objek dengan push_back?

169

Setelah banyak penyelidikan dengan valgrind, saya telah membuat kesimpulan bahwa std :: vector membuat salinan dari objek yang ingin Anda push_back.

Benarkah itu benar? Vektor tidak dapat menyimpan referensi atau penunjuk objek tanpa salinan ?!

Terima kasih

benlaug
sumber
20
Ini adalah prinsip dasar C ++. Objek adalah nilai. Tugas membuat salinan. Dua variabel yang merujuk ke objek yang sama tidak dimungkinkan kecuali Anda memodifikasi tipe dengan *atau &untuk membuat pointer atau referensi.
Daniel Earwicker
8
@DanielEarwicker push_back sebenarnya mengambil referensi. Tidak jelas dari tanda tangan saja bahwa itu akan membuat salinan.
Brian Gordon
3
@ BrianGordon - Tidak mengatakan itu! Oleh karena itu perlunya prinsip panduan. Meski begitu, kita dapat menyimpulkan sesuatu dari tanda tangan push_back: dibutuhkan a const&. Entah itu membuang nilai (tidak berguna), atau ada metode pengambilan. Jadi kita melihat tanda tangan dari back, dan itu mengembalikan polos &, sehingga nilai asli disalin atau consttelah secara diam-diam dibuang (sangat buruk: perilaku yang berpotensi tidak terdefinisi). Jadi dengan asumsi para perancang vectoritu rasional ( vector<bool>tidak tahan) kami menyimpulkan itu membuat salinan.
Daniel Earwicker

Jawaban:

183

Ya, std::vector<T>::push_back()buat salinan argumen dan simpan dalam vektor. Jika Anda ingin menyimpan pointer ke objek di vektor Anda, buat std::vector<whatever*>bukan std::vector<whatever>.

Namun, Anda perlu memastikan bahwa objek yang direferensikan oleh pointer tetap valid sementara vektor memegang referensi untuk mereka (pointer pintar menggunakan idiom RAII memecahkan masalah).

Alexander Gessler
sumber
Saya juga akan mencatat bahwa, jika Anda menggunakan pointer mentah, Anda sekarang bertanggung jawab untuk membersihkannya. Tidak ada alasan bagus untuk melakukan ini (tidak ada yang bisa saya pikirkan), Anda harus selalu menggunakan smart pointer.
Ed S.
1
yang mengatakan, Anda tidak boleh menggunakan std :: auto_ptr dalam wadah stl, untuk info lebih lanjut: mengapa-itu-salah-untuk-menggunakan-stdauto-ptr-dengan-wadah-standar
OriginalCliche
24
Sejak C ++ 11, push_backakan melakukan langkah alih-alih salinan jika argumennya adalah referensi nilai. (Objek dapat dikonversi ke rvalue referensi dengan std::move().)
emlai
2
@tuple_cat komentar Anda harus mengatakan "jika argumennya adalah nilai". (Jika argumennya adalah nama entitas yang dideklarasikan sebagai rvalue reference, maka argumen tersebut sebenarnya merupakan lvalue dan tidak akan dipindahkan dari) - periksa edit saya ke jawaban "Karl Nicoll" yang membuat kesalahan itu awalnya
MM
Ada jawaban di bawah ini tetapi untuk membuatnya lebih jelas: Karena C ++ 11 juga digunakan emplace_backuntuk menghindari penyalinan atau pemindahan (buat objek di tempat yang disediakan oleh wadah).
Wormer
34

Ya, std::vectorsimpan salinannya. Bagaimana seharusnya vectortahu masa hidup yang diharapkan dari objek Anda?

Jika Anda ingin mentransfer atau berbagi kepemilikan objek menggunakan pointer, mungkin pointer cerdas seperti shared_ptr(ditemukan dalam Boost atau TR1 ) untuk memudahkan manajemen sumber daya.

Georg Fritzsche
sumber
3
belajar menggunakan shared_ptr - mereka melakukan apa yang Anda inginkan. Idiom favorit saya adalah typedef boost :: shared_ptr <Foo> FooPtr; Kemudian buat wadah FooPtrs
pm100
3
@ pm100 - Tahukah Anda boost::ptr_vector?
Manuel
2
Saya juga suka menggunakan class Foo { typedef boost::shared_ptr<Foo> ptr; };hanya menulis Foo::ptr.
Rupert Jones
2
@ pm100 - shared_ptrtidak persis api dan lupakan. Lihat stackoverflow.com/questions/327573 dan stackoverflow.com/questions/701456
Daniel Earwicker
2
shared_ptr bagus jika Anda memiliki kepemilikan bersama, tetapi itu biasanya digunakan secara berlebihan. unique_ptr atau boost scoped_ptr lebih masuk akal ketika kepemilikannya jelas.
Nemanja Trifunovic
28

Dari C ++ 11 dan seterusnya, semua kontainer standar ( std::vector,, std::mapdll) mendukung pemindahan semantik, yang berarti bahwa Anda sekarang dapat meneruskan nilai ke kontainer standar dan menghindari salinan:

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

Atau Anda dapat menggunakan berbagai pointer cerdas untuk mendapatkan sebagian besar efek yang sama:

std::unique_ptr contoh

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 contoh

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
sumber
2
Perhatikan bahwa std::make_unique(mengganggu) hanya tersedia di C ++ 14 dan di atas. Pastikan Anda memberi tahu kompiler Anda untuk mengatur kesesuaian standarnya jika Anda ingin mengkompilasi contoh-contoh ini.
Laryx Decidua
Dalam 5a Anda dapat menggunakan auto pFoo =untuk menghindari pengulangan; dan semua std::stringgips dapat dihapus (ada konversi implisit dari string literal ke std::string)
MM
2
@ user465139 make_uniquedapat dengan mudah diimplementasikan dalam C ++ 11, jadi itu hanya sedikit gangguan bagi seseorang yang terjebak dengan kompiler C ++ 11
MM
1
@ MM: Memang. Berikut ini adalah implementasi buku teks:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua
1
@Anakin - Ya mereka harus melakukannya, tetapi hanya jika Anda menyalin. Jika Anda menggunakan std::move()dengan std::shared_ptr, pointer bersama asli mungkin memiliki pointer itu berubah sejak kepemilikan diteruskan ke vektor. Lihat di sini: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Karl Nicoll
15

std :: vector selalu membuat salinan dari apa pun yang disimpan dalam vektor.

Jika Anda menyimpan vektor pointer, maka itu akan membuat salinan dari pointer, tetapi bukan instance yang menunjuk pointer. Jika Anda berurusan dengan objek besar, Anda bisa (dan mungkin harus) selalu menggunakan vektor pointer. Seringkali, menggunakan vektor smart pointer dari tipe yang sesuai adalah baik untuk tujuan keselamatan, karena menangani masa pakai objek dan manajemen memori bisa rumit sebaliknya.

Reed Copsey
sumber
3
itu tidak tergantung pada jenisnya. Itu selalu membuat salinan. Jika penunjuknya membuat salinan penunjuk
pm100
Anda berdua benar. Secara teknis, ya, selalu membuat salinan. Praktis, jika Anda memberikannya sebuah pointer ke objek, itu menyalin pointer, bukan objek. Aman, Anda harus menggunakan smart pointer yang sesuai.
Steven Sudit
1
Ya, selalu menyalin - Namun, "objek" yang dirujuk OP kemungkinan besar adalah kelas atau struct, jadi saya merujuk pada apakah itu menyalin "Objek" tergantung pada definisi. Kata-kata yang buruk.
Reed Copsey
3

Std :: vector tidak hanya membuat salinan dari apa pun yang Anda tekan kembali, tetapi definisi dari koleksi menyatakan bahwa ia akan melakukannya, dan bahwa Anda tidak dapat menggunakan objek tanpa semantik salinan yang benar dalam vektor. Jadi, misalnya, Anda tidak menggunakan auto_ptr dalam vektor.

Liz Albin
sumber
2

Relevan dalam C ++ 11 adalah emplacekeluarga fungsi anggota, yang memungkinkan Anda mentransfer kepemilikan objek dengan memindahkannya ke dalam wadah.

Ungkapan penggunaan akan terlihat seperti

std::vector<Object> objs;

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

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

Langkah untuk objek nilai adalah penting karena jika tidak maka akan diteruskan sebagai referensi atau referensi const dan langkah konstruktor tidak akan dipanggil.

Lemonpi
sumber
0

jika Anda ingin bukan salinannya; maka cara terbaik adalah dengan menggunakan vektor pointer (atau struktur lain yang berfungsi untuk tujuan yang sama). jika Anda ingin salinannya; gunakan langsung push_back (). Anda tidak punya pilihan lain.

rahmivolkan
sumber
1
Catatan tentang vektor penunjuk: vektor <shared_ptr <obj>> jauh lebih aman daripada vektor <obj *> dan shared_ptr adalah bagian dari standar pada tahun lalu.
rich.e
-1

Mengapa butuh banyak penyelidikan valgrind untuk mengetahui hal ini! Buktikan saja kepada diri Anda dengan beberapa kode sederhana misalnya

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

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

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

Jika "hello world" dicetak, objek harus disalin

NexusSquared
sumber
4
Ini bukan bukti. Jika objek tidak disalin, pernyataan terakhir Anda akan menjadi perilaku yang tidak terdefinisi dan dapat mencetak halo.
Mat
4
tes yang benar akan memodifikasi salah satu dari dua setelah penyisipan. Jika mereka adalah objek yang sama (jika vektor menyimpan referensi), keduanya akan dimodifikasi.
Francesco Dondi