Haruskah saya menyimpan seluruh benda, atau petunjuk ke benda dalam wadah?

162

Merancang sistem baru dari awal. Saya akan menggunakan STL untuk menyimpan daftar dan peta objek berumur panjang tertentu.

Pertanyaan: Haruskah saya memastikan objek saya memiliki konstruktor salinan dan menyimpan salinan objek dalam wadah STL saya, atau apakah secara umum lebih baik untuk mengelola kehidupan & ruang lingkup sendiri dan hanya menyimpan pointer ke objek-objek tersebut dalam wadah STL saya?

Saya menyadari ini agak pendek pada detail, tetapi saya mencari jawaban "teoretis" yang lebih baik jika ada, karena saya tahu kedua solusi ini mungkin.

Dua kerugian yang sangat jelas untuk bermain dengan pointer: 1) Saya harus mengatur sendiri alokasi / deallokasi objek-objek ini dalam lingkup di luar STL. 2) Saya tidak bisa membuat objek temp pada stack dan menambahkannya ke wadah saya.

Apakah ada hal lain yang saya lewatkan?

Stéphane
sumber
36
Ya Tuhan, aku suka situs ini, ini adalah pertanyaan EXACT yang saya pikirkan hari ini ... terima kasih telah melakukan pekerjaan memintanya untuk saya :-)
eviljack
2
Hal lain yang menarik adalah bahwa kita harus memeriksa apakah pointer benar-benar ditambahkan ke koleksi dan jika tidak kita harus memanggil delete untuk menghindari kebocoran memori ... if ((set.insert (pointer)). second = false) {delete pointer;}
javapowered

Jawaban:

68

Karena orang-orang berbicara tentang efisiensi penggunaan pointer.

Jika Anda mempertimbangkan untuk menggunakan std :: vector dan jika pembaruan sedikit dan Anda sering beralih ke koleksi Anda dan itu adalah jenis "salinan" objek penyimpanan non-polimorfik akan lebih efisien karena Anda akan mendapatkan lokalitas referensi yang lebih baik.

Otoh, jika pembaruan adalah pointer penyimpanan umum akan menghemat biaya salinan / relokasi.

Torbjörn Gyllebring
sumber
7
Dalam hal lokalitas cache, menyimpan pointer ke vektor bisa efisien jika digunakan bersama dengan pengalokasi kustom untuk pointees. Pengalokasi khusus harus menangani lokalitas cache, misalnya menggunakan penempatan baru (lihat en.wikipedia.org/wiki/Placement_syntax#Custom_allocators ).
amit
47

Ini sangat tergantung pada situasi Anda.

Jika objek Anda kecil, dan melakukan salinan objek itu ringan, maka menyimpan data di dalam wadah stl mudah dan mudah dikelola menurut saya karena Anda tidak perlu khawatir tentang manajemen seumur hidup.

Jika objek Anda besar, dan memiliki konstruktor default tidak masuk akal, atau salinan objek mahal, maka menyimpan dengan pointer mungkin merupakan cara yang harus dilakukan.

Jika Anda memutuskan untuk menggunakan pointer ke objek, lihat di Perpustakaan Boost Pointer Container . Pustaka pendorong ini membungkus semua wadah STL untuk digunakan dengan objek yang dialokasikan secara dinamis.

Setiap wadah penunjuk (misalnya ptr_vector) mengambil kepemilikan suatu objek saat ditambahkan ke wadah, dan mengelola masa pakai objek tersebut untuk Anda. Anda juga mengakses semua elemen dalam wadah ptr_ dengan referensi. Ini memungkinkan Anda melakukan hal-hal seperti

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

Kelas-kelas ini membungkus wadah STL dan bekerja dengan semua algoritma STL, yang sangat berguna.

Ada juga fasilitas untuk mentransfer kepemilikan pointer di wadah ke pemanggil (melalui fungsi rilis di sebagian besar kontainer).

Nick Haddad
sumber
38

Jika Anda menyimpan objek polimorfik, Anda selalu perlu menggunakan koleksi pointer kelas dasar.

Itu adalah jika Anda berencana untuk menyimpan berbagai jenis turunan dalam koleksi Anda, Anda harus menyimpan pointer atau dimakan oleh deamon pengiris.

Torbjörn Gyllebring
sumber
1
Saya menyukai deamon yang mengiris!
idichekop
22

Maaf untuk melompat 3 tahun setelah acara, tetapi catatan peringatan di sini ...

Pada proyek besar terakhir saya, struktur data pusat saya adalah satu set objek yang cukup mudah. Sekitar satu tahun dalam proyek ini, ketika persyaratan berkembang, saya menyadari bahwa objek tersebut sebenarnya perlu polimorfik. Butuh beberapa minggu operasi otak yang sulit dan buruk untuk memperbaiki struktur data menjadi satu set pointer kelas dasar, dan untuk menangani semua kerusakan jaminan dalam penyimpanan objek, casting, dan sebagainya. Butuh beberapa bulan untuk meyakinkan diri saya bahwa kode baru itu berfungsi. Kebetulan, ini membuat saya berpikir keras tentang bagaimana model objek C ++ yang dirancang dengan baik.

Pada proyek besar saya saat ini, struktur data pusat saya adalah satu set objek yang cukup mudah. Sekitar setahun ke proyek (yang kebetulan hari ini), saya menyadari bahwa objek tersebut sebenarnya harus polimorfik. Kembali ke internet, temukan utas ini, dan temukan tautan Nick ke pustaka wadah penunjuk Boost. Inilah yang saya harus tulis terakhir kali untuk memperbaiki semuanya, jadi saya akan mencobanya kali ini.

Moral, bagi saya, bagaimanapun: jika spec Anda tidak 100% dilemparkan ke batu, pergi untuk pointer, dan Anda mungkin berpotensi menghemat banyak pekerjaan nanti.

EML
sumber
Spesifikasi tidak pernah ditetapkan di atas batu. Saya tidak berpikir itu berarti Anda harus menggunakan penunjuk menggunakan wadah secara eksklusif, meskipun penunjuk penunjuk tampaknya membuat opsi itu jauh lebih menarik. Saya skeptis bahwa Anda perlu merombak keseluruhan program sekaligus jika Anda memutuskan bahwa sebuah objek kontainer harus dikonversi menjadi sebuah wadah pointer. Ini mungkin terjadi di bawah beberapa desain. Dalam hal ini, itu adalah desain yang rapuh. Dalam hal itu, jangan salahkan masalah Anda pada "kelemahan" wadah objek.
allyourcode
Anda bisa meninggalkan item dalam vektor dengan semantik nilai dan melakukan perilaku polimorfik di dalamnya.
Billy ONeal
19

Mengapa tidak mendapatkan yang terbaik dari kedua dunia: lakukan wadah petunjuk pintar (seperti boost::shared_ptratau std::shared_ptr). Anda tidak harus mengelola memori, dan Anda tidak harus berurusan dengan operasi penyalinan besar.

Branan
sumber
Bagaimana pendekatan ini berbeda dari apa yang disarankan Nick Haddad dengan menggunakan Perpustakaan Penampung Boost Pointer?
Thorsten Schöning
10
@ ThorstenSchöning std :: shared_ptr tidak menambahkan ketergantungan pada peningkatan.
James Johnston
Anda tidak dapat menggunakan pointer bersama untuk menangani polimorfisme Anda sehingga Anda akhirnya akan kehilangan fitur ini dengan pendekatan ini, kecuali jika Anda secara eksplisit melemparkan pointer
auserdude
11

Umumnya menyimpan objek secara langsung dalam wadah STL adalah yang terbaik karena paling sederhana, paling efisien, dan paling mudah untuk menggunakan objek.

Jika objek Anda sendiri memiliki sintaks yang tidak dapat disalin atau merupakan tipe basis abstrak, Anda harus menyimpan pointer (yang paling mudah adalah menggunakan shared_ptr)

Greg Rogers
sumber
4
Ini tidak paling efisien jika objek Anda besar, dan Anda sering memindahkan elemen.
allyourcode
3

Anda tampaknya memiliki pemahaman yang baik tentang perbedaan itu. Jika objek kecil dan mudah disalin, maka simpanlah.

Jika tidak, saya akan berpikir tentang menyimpan pointer pintar (bukan auto_ptr, ref pointer pintar menghitung) untuk yang Anda alokasikan pada heap. Jelas, jika Anda memilih pointer pintar, maka Anda tidak dapat menyimpan objek temp stack yang dialokasikan (seperti yang Anda katakan).

@ Torbjörn membuat poin bagus tentang mengiris.

Lou Franco
sumber
1
Oh, dan tidak pernah pernah pernah membuat koleksi auto_ptr ini
Torbjörn Gyllebring
Benar, auto_ptr bukan pointer pintar - itu tidak dihitung.
Lou Franco
auto_ptr juga tidak memiliki salinan semantik yang tidak merusak. Tindakan penugasan auto_ptr dari oneke anotherakan merilis referensi dari onedan mengubah one.
Andy Finkenstadt
2

Jika objek akan dirujuk ke tempat lain dalam kode, simpan dalam vektor boost :: shared_ptr. Ini memastikan bahwa pointer ke objek akan tetap valid jika Anda mengubah ukuran vektor.

Yaitu:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

Jika tidak ada orang lain yang menyimpan pointer ke objek, atau daftar tidak tumbuh dan menyusut, simpan saja sebagai objek tua-polos:

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

sumber
1

Pertanyaan ini telah mengganggu saya untuk sementara waktu.

Saya cenderung untuk menyimpan pointer, tetapi saya memiliki beberapa persyaratan tambahan (pembungkus SWIG lua) yang mungkin tidak berlaku untuk Anda.

Poin paling penting dalam postingan ini adalah untuk mengujinya sendiri , menggunakan objek Anda

Saya melakukan ini hari ini untuk menguji kecepatan memanggil fungsi anggota pada koleksi 10 juta objek, 500 kali.

Fungsi memperbarui x dan y berdasarkan xdir dan ydir (semua variabel anggota float).

Saya menggunakan std :: list untuk menampung kedua jenis objek, dan saya menemukan bahwa menyimpan objek dalam daftar sedikit lebih cepat daripada menggunakan pointer. Di sisi lain, kinerjanya sangat dekat, sehingga tergantung pada bagaimana mereka akan digunakan dalam aplikasi Anda.

Untuk referensi, dengan -O3 pada perangkat keras saya pointer butuh 41 detik untuk menyelesaikan dan objek mentah butuh 30 detik untuk menyelesaikan.

Meleneth
sumber