Mengapa pustaka dan kerangka kerja C ++ tidak pernah menggunakan pointer pintar?

156

Saya membaca di beberapa artikel bahwa pointer mentah hampir tidak boleh digunakan. Alih-alih, mereka harus selalu dibungkus dengan pointer pintar, apakah itu scoped atau shared pointer.

Namun, saya perhatikan bahwa kerangka kerja seperti Qt, wxWidgets dan perpustakaan seperti Boost tidak pernah kembali atau mengharapkan pointer pintar, seolah-olah mereka tidak menggunakannya sama sekali. Sebaliknya, mereka kembali atau mengharapkan pointer mentah. Apakah ada alasan untuk itu? Haruskah saya menjauh dari smart pointer ketika saya menulis API publik, dan mengapa?

Hanya ingin tahu mengapa pointer cerdas direkomendasikan ketika banyak proyek besar tampaknya menghindarinya.

laurent
sumber
22
Semua perpustakaan yang baru saja Anda beri nama sudah dimulai bertahun-tahun yang lalu. Pointer pintar hanya menjadi benar-benar standar dalam C ++ 11.
chrisaycock
22
pointer pintar memang memiliki overhead (penghitungan referensi dll) - yang mungkin penting - dalam sistem embedded / real time misalnya. IMHO - pointer cerdas adalah untuk programmer yang malas. Juga banyak API yang menggunakan penyebut umum terendah. Aku merasakan api menjilati kakiku saat aku mengetik!
Ed Heal
93
@EdHeal: Alasan Anda bisa merasakan api menjilati kaki Anda adalah karena Anda sepenuhnya salah dalam segala hal. Misalnya, ada overhead apa unique_ptr? Tidak ada sama sekali. Apakah Qt / WxWidget ditargetkan pada sistem embedded atau real time? Tidak, itu dimaksudkan untuk Windows / Mac / Unix di desktop paling banyak. Pointer pintar adalah untuk programmer yang ingin memperbaikinya.
Anak anjing
24
Sungguh, ponsel menjalankan Java.
R. Martinho Fernandes
12
Pointer pintar hanya benar-benar standar dalam C ++ 11? Apa??? Benda-benda ini telah digunakan selama lebih dari 20 tahun.
Kaz

Jawaban:

124

Terlepas dari kenyataan bahwa banyak perpustakaan ditulis sebelum munculnya smart pointer standar, alasan terbesar mungkin adalah kurangnya standar C ++ Application Binary Interface (ABI).

Jika Anda menulis pustaka header saja, Anda bisa membagikan smart pointer dan kontainer standar ke isi hati Anda. Sumbernya tersedia untuk pustaka Anda pada waktu kompilasi, jadi Anda mengandalkan stabilitas antarmuka mereka sendiri, bukan pada implementasinya.

Tetapi karena kurangnya ABI standar, Anda umumnya tidak dapat melewatkan objek-objek ini dengan aman melintasi batas-batas modul. GCC shared_ptrmungkin berbeda dari MSVC shared_ptr, yang juga bisa berbeda dari Intel shared_ptr. Bahkan dengan kompiler yang sama , kelas-kelas ini tidak dijamin biner kompatibel antar versi.

Intinya adalah bahwa jika Anda ingin mendistribusikan versi prebuilt perpustakaan Anda, Anda memerlukan ABI standar untuk dapat diandalkan. C tidak memilikinya, tetapi vendor kompiler sangat bagus dalam hal interoperabilitas antara pustaka C untuk platform tertentu — ada standar de facto.

Situasinya tidak sebaik C ++. Kompiler individual dapat menangani interoperasi antara biner mereka sendiri, sehingga Anda memiliki opsi untuk mendistribusikan versi untuk setiap kompiler yang didukung, seringkali GCC dan MSVC. Tetapi mengingat hal ini, sebagian besar perpustakaan hanya mengekspor antarmuka C — dan itu berarti pointer mentah.

Namun, kode non-perpustakaan umumnya lebih suka pointer pintar daripada mentah.

Jon Purdy
sumber
17
Saya setuju dengan Anda, bahkan melewati string std :: dapat menjadi masalah, Ini mengatakan banyak tentang C ++ sebagai "bahasa yang bagus untuk perpustakaan".
Ha11owed
8
Intinya lebih seperti: jika Anda ingin mendistribusikan versi prebuild, Anda harus melakukannya untuk setiap kompiler yang ingin Anda dukung.
josefx
6
@ josefx: Ya ini menyedihkan tapi benar, satu-satunya alternatif adalah COM atau antarmuka C mentah. Saya berharap C ++ comity akan mulai mengkhawatirkan masalah semacam ini. Maksud saya bukan seperti C ++ adalah bahasa baru dari 2 tahun yang lalu.
Robot Mess
3
Saya menurunkan suara karena ini salah. Masalah ABI lebih dari dapat dikelola dalam banyak kasus. Meski tidak mudah digunakan, ABI juga sulit diatasi.
Puppy
4
@NathanAdams: Perangkat lunak seperti itu tidak diragukan lagi mengesankan dan bermanfaat. Tapi itu memperlakukan gejala dari masalah yang lebih dalam: C ++ semantik seumur hidup dan kepemilikan adalah suatu tempat antara miskin dan tidak ada. Bug tumpukan itu tidak akan muncul jika bahasa tidak mengizinkannya. Jadi tentu saja, smart pointer bukanlah obat mujarab — mereka adalah upaya untuk mengganti sebagian kerugian yang ditimbulkan dengan menggunakan C ++.
Jon Purdy
40

Mungkin ada banyak alasan. Untuk membuat daftar beberapa di antaranya:

  1. Pointer pintar menjadi bagian dari standar baru-baru ini. Sampai saat itu mereka adalah bagian dari perpustakaan lain
  2. Penggunaan utama mereka adalah untuk menghindari kebocoran memori; banyak perpustakaan tidak memiliki manajemen memori sendiri; Umumnya mereka menyediakan utilitas dan API
  3. Mereka diimplementasikan sebagai pembungkus, karena mereka sebenarnya objek dan bukan pointer. Yang memiliki biaya waktu / ruang tambahan, dibandingkan dengan pointer mentah; Para pengguna perpustakaan mungkin tidak ingin memiliki overhead seperti itu

Sunting : Menggunakan smart pointer adalah pilihan sepenuhnya pengembang. Itu tergantung pada berbagai faktor.

  1. Dalam sistem kritis kinerja, Anda mungkin tidak ingin menggunakan smart pointer yang menghasilkan overhead

  2. Proyek yang membutuhkan kompatibilitas ke belakang, Anda mungkin tidak ingin menggunakan smart pointer yang memiliki fitur khusus C ++ 11

Sunting2 Ada serangkaian beberapa downvotes dalam rentang 24 jam karena bagian di bawah ini. Saya gagal memahami mengapa jawabannya diturunkan meskipun di bawah ini hanya saran tambahan dan bukan jawaban.
Namun, C ++ selalu memfasilitasi Anda untuk membuka opsi. :) mis

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};

Dan dalam kode Anda menggunakannya sebagai:

Pointer<int>::type p;

Bagi mereka yang mengatakan bahwa smart pointer dan raw pointer berbeda, saya setuju dengan itu. Kode di atas hanyalah sebuah ide di mana seseorang dapat menulis kode yang dapat dipertukarkan hanya dengan a #define, ini bukan paksaan ;

Sebagai contoh, T*harus dihapus secara eksplisit tetapi pointer pintar tidak. Kita dapat memiliki templated Destroy()untuk mengatasinya.

template<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

dan gunakan sebagai:

Destroy(p);

Dengan cara yang sama, untuk pointer mentah kita dapat menyalinnya secara langsung dan untuk smart pointer kita dapat menggunakan operasi khusus.

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));

Di mana Assign()adalah sebagai:

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}
iammilind
sumber
14
Pada 3. Beberapa pointer cerdas memiliki biaya waktu / ruang tambahan, yang lain tidak, termasuk std::auto_ptryang telah menjadi bagian dari standar untuk waktu yang lama (dan perhatikan, saya memang suka std::auto_ptrsebagai tipe pengembalian untuk fungsi yang membuat objek, bahkan jika itu adalah hampir tidak berguna di tempat lain). Dalam C ++ 11 std::unique_ptrtidak memiliki biaya tambahan atas pointer biasa.
David Rodríguez - dribeas
4
Tepatnya ... ada simetri yang bagus pada penampilan unique_ptrdan menghilangnya auto_ptr, penargetan kode C ++ 03 harus menggunakan yang lebih baru, sementara penargetan kode C ++ 11 dapat menggunakan yang sebelumnya. Pointer pintar tidak shared_ptr , ada banyak standar dan tidak ada standar, termasuk proposal dengan standar yang ditolak sebagaimanaged_ptr
David Rodríguez - dribeas
2
@ iammilind, itu adalah poin yang menarik, tetapi lucunya adalah jika kita akhirnya menggunakan smart pointer, seperti yang banyak direkomendasikan, kita akhirnya membuat kode yang tidak kompatibel dengan perpustakaan utama. Tentu saja, kita dapat membungkus / membuka bungkusan smart pointer sesuai kebutuhan tetapi sepertinya banyak kesulitan dan akan membuat kode tidak konsisten (kadang kita berurusan dengan smart pointer, kadang tidak).
laurent
7
Pernyataan bahwa smart pointer memiliki "tambahan waktu / biaya ruang" agak menyesatkan; semua pointer pintar kecuali unique_ptrbiaya runtime, tetapi unique_ptrsejauh ini adalah salah satu yang paling umum digunakan. Sampel kode yang Anda berikan juga menyesatkan, karena unique_ptrdan T*sepenuhnya merupakan konsep yang berbeda. Fakta bahwa Anda merujuk keduanya sama-sama typememberikan kesan bahwa mereka dapat saling bertukar satu sama lain.
void-pointer
12
Anda tidak dapat mengetik seperti itu, jenis ini tidak setara. Menulis typedef seperti ini meminta masalah.
Alex B
35

Ada dua masalah dengan smart pointer (pra C ++ 11):

  • non-standar, sehingga masing-masing perpustakaan cenderung menemukan kembali sendiri (sindrom NIH & masalah dependensi)
  • biaya potensial

The standar pointer cerdas, dalam hal ini adalah bebas biaya, adalah unique_ptr. Sayangnya itu membutuhkan semantik bergerak C ++ 11, yang hanya muncul baru-baru ini. Semua pointer pintar lainnya memiliki biaya ( shared_ptr, intrusive_ptr) atau memiliki semantik yang kurang ideal ( auto_ptr).

Dengan C ++ 11 di tikungan, membawa std::unique_ptr, orang akan tergoda untuk berpikir bahwa itu akhirnya berakhir ... Saya tidak begitu optimis.

Hanya beberapa kompiler utama yang mengimplementasikan sebagian besar C ++ 11, dan hanya dalam versi terbaru mereka. Kita dapat mengharapkan perpustakaan besar seperti QT dan Boost untuk bersedia mempertahankan kompatibilitas dengan C ++ 03 untuk sementara waktu, yang agak menghalangi adopsi luas dari smart pointer yang baru dan mengkilap.

Matthieu M.
sumber
12

Anda tidak boleh tinggal jauh dari smart pointer, mereka menggunakannya terutama dalam aplikasi di mana Anda harus melewati objek.

Perpustakaan cenderung hanya mengembalikan nilai atau mengisi objek. Mereka biasanya tidak memiliki objek yang perlu digunakan di banyak tempat, jadi tidak perlu bagi mereka untuk menggunakan smart pointer (setidaknya tidak dalam antarmuka mereka, mereka dapat menggunakannya secara internal).

Saya dapat mengambil contoh perpustakaan yang telah kami kerjakan, di mana setelah beberapa bulan pengembangan saya menyadari bahwa kami hanya menggunakan pointer dan smart pointer di beberapa kelas (3-5% dari semua kelas).

Melewati variabel dengan referensi sudah cukup di sebagian besar tempat, kami menggunakan pointer pintar setiap kali kami memiliki objek yang bisa nol, dan pointer mentah ketika perpustakaan yang kami gunakan memaksa kami untuk.

Sunting (Saya tidak dapat berkomentar karena reputasi saya): meneruskan variabel dengan referensi sangat fleksibel: jika Anda ingin objek dibaca hanya Anda dapat menggunakan referensi const (Anda masih dapat melakukan beberapa gips jahat untuk dapat menulis objek) ) tetapi Anda mendapatkan perlindungan maksimal (sama dengan smart pointer). Tapi saya setuju bahwa itu jauh lebih baik untuk hanya mengembalikan objek.

Mess Robot
sumber
Saya tidak setuju, dengan Anda, tepatnya, tetapi saya akan menunjukkan bahwa ada mazhab pemikiran yang mencela berlalunya berlalunya referensi variabel dalam banyak kasus. Saya mengaku bahwa saya mematuhi sekolah itu. Saya lebih suka fungsi tidak mengubah argumen mereka. Bagaimanapun, sejauh yang saya tahu, referensi variabel C ++ tidak melakukan apa pun untuk mencegah kesalahan penanganan objek yang mereka rujuk, itulah yang ingin dilakukan oleh pointer cerdas.
thb
2
Anda memiliki const untuk itu (sepertinya saya bisa berkomentar: D).
Robot Mess
9

Qt tanpa sengaja menemukan kembali banyak bagian dari pustaka Standar dalam upaya untuk menjadi Java. Saya percaya bahwa itu memang memiliki pointer pintar sendiri sekarang, tetapi secara umum, itu bukan puncak dari desain. wxWidgets, sejauh yang saya ketahui, dirancang jauh sebelum pointer pintar yang dapat digunakan ditulis.

Adapun Boost, saya sepenuhnya berharap mereka menggunakan pointer pintar di mana pun sesuai. Anda mungkin harus lebih spesifik.

Selain itu, jangan lupa bahwa pointer cerdas ada untuk menegakkan kepemilikan. Jika API tidak memiliki semantik kepemilikan, lalu mengapa menggunakan smart pointer?

Anak anjing
sumber
19
Qt ditulis sebelum sebagian besar fungsionalitas tersebar luas pada platform yang ingin digunakan. Ini telah memiliki pointer cerdas untuk waktu yang lama, dan menggunakannya untuk melakukan berbagi sumber daya secara implisit di hampir semua kelas Q *.
rubenvb
6
Setiap pustaka GUI secara tidak perlu menemukan kembali roda. Bahkan string, Qt telah QString, wxWidgets miliki wxString, MFC memiliki nama yang mengerikan CString. Bukankah UTF-8 std::stringcukup baik untuk 99% tugas GUI?
Invers
10
@Inverse QString dibuat ketika std :: string tidak ada.
MrFox
Periksa kapan qt dibuat dan pointer pintar apa yang tersedia pada saat itu.
Dainius
3

Pertanyaan bagus. Saya tidak tahu artikel spesifik yang Anda referensikan, tetapi saya telah membaca hal-hal serupa dari waktu ke waktu. Kecurigaan saya adalah bahwa para penulis artikel semacam itu cenderung memiliki bias terhadap pemrograman gaya C ++. Jika penulis memprogram dalam C ++ hanya ketika ia harus, kemudian kembali ke Jawa atau secepatnya, maka ia tidak benar-benar berbagi pola pikir C ++.

Seseorang mencurigai bahwa beberapa atau sebagian besar penulis yang sama lebih suka pengelola memori pengumpul sampah. Saya tidak, tetapi saya berpikir berbeda dari mereka.

Pointer pintar bagus, tetapi mereka harus menjaga jumlah referensi. Menyimpan jumlah referensi dikenakan biaya - sering biaya sederhana, tetapi biaya tetap - pada saat runtime. Tidak ada yang salah dengan menghemat biaya ini dengan menggunakan pointer kosong, terutama jika pointer dikelola oleh destruktor.

Salah satu hal terbaik tentang C ++ adalah dukungannya untuk pemrograman embedded-system. Penggunaan bare pointer adalah bagian dari itu.

Pembaruan: Seorang komentator telah mengamati dengan benar bahwa C ++ yang baru unique_ptr(tersedia sejak TR1) tidak menghitung referensi. Komentator juga memiliki definisi "penunjuk pintar" yang berbeda dari yang ada dalam pikiran saya. Dia mungkin benar tentang definisi itu.

Pembaruan lebih lanjut: Utas komentar di bawah ini menyinari. Semua itu dianjurkan dibaca.

thb
sumber
2
Sebagai permulaan, pemrograman embedded-system adalah minoritas luas dari semua pemrograman, dan sangat tidak relevan. C ++ adalah bahasa tujuan umum. Kedua, shared_ptrsimpan penghitungan referensi. Ada banyak jenis penunjuk pintar lainnya yang tidak memiliki jumlah referensi sama sekali. Akhirnya, perpustakaan yang disebutkan ditargetkan pada platform yang memiliki banyak sumber daya cadangan. Bukan karena saya yang downvoter, tapi yang saya katakan adalah bahwa posting Anda penuh dengan kesalahan.
Anak anjing
2
@ THB - Saya setuju dengan Anda sentimen. DeadMG - Silakan coba hidup tanpa sistem tertanam. Ya - beberapa pointer cerdas tidak memiliki overhead, tetapi beberapa pointer. OP menyebutkan perpustakaan. Boost misalnya memiliki bagian-bagian yang digunakan oleh sistem embedded - tetapi smart pointer mungkin tidak sesuai untuk aplikasi tertentu.
Ed Heal
2
@ Edheal: Tidak hidup tanpa sistem yang disematkan! = Pemrograman untuk mereka bukanlah minoritas yang kecil, tidak relevan,. Pointer pintar sesuai untuk setiap situasi di mana Anda perlu mengelola masa pakai sumber daya.
Anak anjing
4
shared_ptrtidak memiliki overhead. Ini hanya memiliki overhead jika Anda tidak memerlukan semantik kepemilikan bersama aman, yang disediakannya.
R. Martinho Fernandes
1
Tidak, shared_ptr memiliki overhead yang signifikan di atas jumlah minimum yang diperlukan untuk semantik kepemilikan bersama thread-safe; khusus itu mengalokasikan blok tumpukan terpisah dari objek aktual yang Anda bagikan, untuk tujuan tunggal menyimpan refcount. intrusive_ptr lebih efisien, tetapi (seperti shared_ptr) juga mengasumsikan bahwa setiap pointer ke objek akan menjadi intrusive_ptr. Anda bisa mendapatkan overhead yang lebih rendah daripada intrusive_ptr dengan pointer berbagi penghitungan ulang kustom, seperti yang saya lakukan di aplikasi saya, dan kemudian menggunakan T * setiap kali Anda dapat menjamin bahwa setidaknya satu pointer cerdas akan hidup lebih lama dari nilai T *.
Qwertie
2

Ada juga tipe smart pointer lainnya. Anda mungkin ingin penunjuk pintar khusus untuk sesuatu seperti replikasi jaringan (yang mendeteksi apakah itu diakses dan mengirimkan modifikasi apa pun ke server atau semacamnya), menyimpan sejarah perubahan, menandai fakta bahwa itu diakses sehingga dapat diselidiki ketika Anda menyimpan data ke disk dan sebagainya. Tidak yakin apakah melakukan hal itu di pointer adalah solusi terbaik, tetapi menggunakan smart pointer yang ada di perpustakaan dapat mengakibatkan orang terkunci di dalamnya dan kehilangan fleksibilitas.

Orang dapat memiliki semua jenis persyaratan dan solusi manajemen memori yang berbeda di luar smart pointer. Saya mungkin ingin mengelola memori sendiri, saya bisa mengalokasikan ruang untuk hal-hal dalam kumpulan memori sehingga dialokasikan terlebih dahulu dan bukan pada saat runtime (berguna untuk game). Saya mungkin menggunakan implementasi pengumpulan sampah C ++ (C ++ 11 memungkinkan hal ini terjadi walaupun belum ada). Atau mungkin saya hanya tidak melakukan sesuatu yang cukup lanjut untuk khawatir tentang mengganggu mereka, saya bisa tahu bahwa saya tidak akan lupa untuk objek yang tidak diinisialisasi dan sebagainya. Mungkin saya hanya percaya diri dengan kemampuan saya mengelola memori tanpa penunjuk pointer.

Integrasi dengan C juga merupakan masalah lain.

Masalah lainnya adalah smart pointer adalah bagian dari STL. C ++ dirancang agar dapat digunakan tanpa STL.

David C. Bishop
sumber
" Masalah lain adalah smart pointer adalah bagian dari STL. "
curiousguy
0

Ini juga tergantung pada domain tempat Anda bekerja. Saya menulis mesin gim untuk mencari nafkah, kami menghindari dorongan seperti wabah, di gim biaya tambahan tidak dapat diterima. Di mesin inti kami, kami akhirnya menulis versi kami sendiri stl (Sama seperti ea stl).

Jika saya menulis formulir aplikasi, saya mungkin mempertimbangkan untuk menggunakan smart pointer; tetapi begitu manajemen memori adalah sifat kedua, tidak memiliki kendali granular atas memori menjadi sangat mengganggu.

Davis Jelek
sumber
3
Tidak ada yang namanya "overhead of boost".
curiousguy
4
Saya belum pernah berbagi-pakai memperlambat mesin gim saya sampai tingkat tertentu. Mereka telah mempercepat proses produksi dan debugging. Juga, apa sebenarnya yang Anda maksud dengan "overhead of boost?" Itu selimut yang cukup besar untuk dilemparkan.
derpface
@curiousguy: Ini adalah overhead kompilasi dari semua header dan template makro + voodoo ...
einpoklum