Mengapa saya harus menggunakan pointer daripada objek itu sendiri?

1602

Saya berasal dari latar belakang Java dan sudah mulai bekerja dengan objek di C ++. Tetapi satu hal yang terpikir oleh saya adalah bahwa orang sering menggunakan pointer ke objek daripada objek itu sendiri, misalnya deklarasi ini:

Object *myObject = new Object;

daripada:

Object myObject;

Atau alih-alih menggunakan fungsi, katakanlah testFunc(), seperti ini:

myObject.testFunc();

kita harus menulis:

myObject->testFunc();

Tetapi saya tidak tahu mengapa kita harus melakukannya dengan cara ini. Saya akan menganggap itu ada hubungannya dengan efisiensi dan kecepatan karena kita mendapatkan akses langsung ke alamat memori. Apakah saya benar?

gEdringer
sumber
405
Pujian bagi Anda untuk mempertanyakan praktik ini daripada sekadar mengikutinya. Sebagian besar waktu, pointer terlalu banyak digunakan.
Luchian Grigore
120
Jika Anda tidak melihat alasan untuk menggunakan pointer, jangan. Lebih suka benda. Lebih suka objek sebelum unique_ptr sebelum shared_ptr sebelum pointer mentah.
Stefan
113
Catatan: di java, semuanya (kecuali tipe dasar) adalah pointer. jadi Anda lebih baik bertanya sebaliknya: mengapa saya perlu benda sederhana?
Karoly Horvath
119
Perhatikan bahwa, di Jawa, pointer disembunyikan oleh sintaks. Dalam C ++, perbedaan antara pointer dan non-pointer dibuat eksplisit dalam kode. Java menggunakan pointer di mana-mana.
Daniel Martín
216
Tutup terlalu luas ? Serius? Tolong orang-orang, perhatikan bahwa cara pemrograman Java ++ ini sangat umum dan merupakan salah satu masalah terpenting pada komunitas C ++ . Ini harus diperlakukan dengan serius.
Manu343726

Jawaban:

1575

Sangat disayangkan bahwa Anda melihat alokasi dinamis begitu sering. Itu hanya menunjukkan berapa banyak programmer C ++ yang buruk.

Dalam arti tertentu, Anda memiliki dua pertanyaan yang digabungkan menjadi satu. Yang pertama adalah kapan kita harus menggunakan alokasi dinamis (menggunakan new)? Yang kedua adalah kapan kita harus menggunakan pointer?

Pesan bawa pulang yang penting adalah bahwa Anda harus selalu menggunakan alat yang sesuai untuk pekerjaan itu . Di hampir semua situasi, ada sesuatu yang lebih tepat dan lebih aman daripada melakukan alokasi dinamis manual dan / atau menggunakan pointer mentah.

Alokasi dinamis

Dalam pertanyaan Anda, Anda telah menunjukkan dua cara membuat objek. Perbedaan utama adalah durasi penyimpanan objek. Saat melakukan Object myObject;dalam blok, objek dibuat dengan durasi penyimpanan otomatis, yang berarti akan dihancurkan secara otomatis ketika keluar dari ruang lingkup. Ketika Anda melakukannya new Object(), objek memiliki durasi penyimpanan dinamis, yang berarti ia tetap hidup sampai Anda secara eksplisit delete. Anda hanya harus menggunakan durasi penyimpanan dinamis saat Anda membutuhkannya. Artinya, Anda harus selalu lebih suka membuat objek dengan durasi penyimpanan otomatis ketika Anda bisa .

Dua situasi utama di mana Anda mungkin memerlukan alokasi dinamis:

  1. Anda perlu objek untuk hidup lebih lama dari ruang lingkup saat ini - objek tertentu di lokasi memori tertentu, bukan salinannya. Jika Anda baik-baik saja dengan menyalin / memindahkan objek (sebagian besar waktu Anda seharusnya), Anda harus memilih objek otomatis.
  2. Anda perlu mengalokasikan banyak memori , yang dapat dengan mudah mengisi tumpukan. Akan lebih baik jika kita tidak perlu khawatir dengan hal ini (sebagian besar waktu Anda tidak harus melakukannya), karena itu benar-benar di luar lingkup C ++, tetapi sayangnya, kita harus berurusan dengan realitas sistem kami sedang mengembangkan.

Ketika Anda benar-benar membutuhkan alokasi dinamis, Anda harus merangkumnya dalam smart pointer atau jenis lain yang melakukan RAII (seperti wadah standar). Pointer pintar memberikan semantik kepemilikan objek yang dialokasikan secara dinamis. Lihatlah std::unique_ptrdan std::shared_ptr, misalnya. Jika Anda menggunakannya dengan benar, Anda hampir sepenuhnya dapat menghindari melakukan manajemen memori Anda sendiri (lihat Aturan Nol ).

Pointer

Namun, ada kegunaan lain yang lebih umum untuk pointer mentah di luar alokasi dinamis, tetapi sebagian besar memiliki alternatif yang harus Anda pilih. Seperti sebelumnya, selalu lebih suka alternatif kecuali Anda benar-benar membutuhkan petunjuk .

  1. Anda perlu referensi semantik . Terkadang Anda ingin meneruskan objek menggunakan pointer (terlepas dari bagaimana itu dialokasikan) karena Anda ingin fungsi yang Anda lewati memiliki akses ke objek tertentu (bukan salinannya). Namun, dalam sebagian besar situasi, Anda harus lebih memilih tipe referensi daripada pointer, karena ini memang khusus dirancang untuk mereka. Perhatikan ini tidak selalu tentang memperpanjang umur objek di luar cakupan saat ini, seperti dalam situasi 1 di atas. Seperti sebelumnya, jika Anda baik-baik saja dengan mengirimkan salinan objek, Anda tidak perlu semantik referensi.

  2. Anda perlu polimorfisme . Anda hanya dapat memanggil fungsi secara polimorfik (yaitu, sesuai dengan tipe dinamis suatu objek) melalui penunjuk atau referensi ke objek. Jika itu perilaku yang Anda butuhkan, maka Anda perlu menggunakan pointer atau referensi. Sekali lagi, referensi harus lebih disukai.

  3. Anda ingin menyatakan bahwa suatu objek adalah opsional dengan membiarkan a nullptrditeruskan ketika objek tersebut dihilangkan. Jika ini argumen, Anda sebaiknya menggunakan argumen default atau fungsi yang berlebihan. Jika tidak, Anda sebaiknya menggunakan tipe yang merangkum perilaku ini, seperti std::optional(diperkenalkan dalam C ++ 17 - dengan standar C ++ sebelumnya, gunakan boost::optional).

  4. Anda ingin memisahkan unit kompilasi untuk meningkatkan waktu kompilasi . Properti berguna dari pointer adalah bahwa Anda hanya memerlukan deklarasi maju dari tipe menunjuk-ke ​​(untuk benar-benar menggunakan objek, Anda akan memerlukan definisi). Ini memungkinkan Anda untuk memisahkan bagian-bagian dari proses kompilasi Anda, yang secara signifikan dapat meningkatkan waktu kompilasi. Lihat idiom Pimpl .

  5. Anda perlu berinteraksi dengan perpustakaan C atau perpustakaan gaya-C. Pada titik ini, Anda terpaksa menggunakan pointer mentah. Hal terbaik yang dapat Anda lakukan adalah memastikan Anda hanya membiarkan pointer mentah Anda lepas pada saat-saat terakhir. Anda bisa mendapatkan pointer mentah dari pointer cerdas, misalnya, dengan menggunakan getfungsi anggotanya. Jika pustaka melakukan beberapa alokasi untuk Anda yang ia harapkan akan Anda alokasikan melalui pegangan, Anda sering dapat membungkus gagang di pointer cerdas dengan deleter kustom yang akan membatalkan alokasi objek dengan tepat.

Joseph Mansfield
sumber
83
"Kamu membutuhkan objek untuk hidup lebih lama dari lingkup saat ini." - Catatan tambahan tentang ini: ada kasus di mana sepertinya Anda membutuhkan objek untuk hidup lebih lama dari ruang lingkup saat ini, tetapi sebenarnya Anda tidak. Jika Anda meletakkan objek Anda di dalam vektor, misalnya, objek akan disalin (atau dipindahkan) ke vektor, dan objek asli aman untuk dihancurkan ketika cakupannya berakhir.
25
Ingat itu s / copy / pindah / di banyak tempat sekarang. Mengembalikan objek pasti tidak berarti bergerak. Anda juga harus mencatat bahwa mengakses suatu objek melalui sebuah pointer adalah ortogonal terhadap bagaimana ia dibuat.
Puppy
15
Saya kehilangan referensi eksplisit untuk RAII pada jawaban ini. C ++ adalah semua (hampir semua) tentang manajemen sumber daya, dan RAII adalah cara untuk melakukannya pada C ++ (Dan masalah utama yang dihasilkan oleh pointer mentah: Breaking RAII)
Manu343726
11
Pointer pintar ada sebelum C ++ 11, misalnya boost :: shared_ptr dan boost :: scoped_ptr. Proyek-proyek lain memiliki padanannya sendiri. Anda tidak dapat memindahkan semantik, dan std :: auto_ptr's assign cacat, jadi C ++ 11 meningkatkan banyak hal, tetapi sarannya masih bagus. (Dan nitpick sedih, itu tidak cukup untuk memiliki akses ke sebuah C ++ 11 compiler, itu perlu bahwa semua kompiler Anda mungkin mungkin ingin kode Anda bekerja dengan dukungan C ++ 11. Ya, Oracle Solaris Studio, aku melihatmu.)
armb
7
@ MDMoore313 Anda dapat menulisObject myObject(param1, etc...)
user000001
173

Ada banyak kasus penggunaan untuk pointer.

Perilaku polimorfik . Untuk tipe polimorfik, pointer (atau referensi) digunakan untuk menghindari pemotongan:

class Base { ... };
class Derived : public Base { ... };

void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }

Derived d;
fun(d);    // oops, all Derived parts silently "sliced" off
gun(&d);   // OK, a Derived object IS-A Base object
hun(d);    // also OK, reference also doesn't slice

Referensi semantik dan menghindari penyalinan . Untuk jenis non-polimorfik, penunjuk (atau referensi) akan menghindari menyalin objek yang berpotensi mahal

Base b;
fun(b);  // copies b, potentially expensive 
gun(&b); // takes a pointer to b, no copying
hun(b);  // regular syntax, behaves as a pointer

Perhatikan bahwa C ++ 11 telah memindahkan semantik yang dapat menghindari banyak salinan objek mahal ke dalam argumen fungsi dan sebagai nilai pengembalian. Tetapi menggunakan pointer pasti akan menghindari itu dan akan memungkinkan beberapa pointer pada objek yang sama (sedangkan objek hanya dapat dipindahkan dari satu kali).

Akuisisi sumber daya . Membuat pointer ke sumber daya menggunakan newoperator adalah anti-pola dalam C ++ modern. Gunakan kelas sumber daya khusus (salah satu wadah Standar) atau penunjuk pintar ( std::unique_ptr<>atau std::shared_ptr<>). Mempertimbangkan:

{
    auto b = new Base;
    ...       // oops, if an exception is thrown, destructor not called!
    delete b;
}

vs.

{
    auto b = std::make_unique<Base>();
    ...       // OK, now exception safe
}

Pointer mentah hanya boleh digunakan sebagai "tampilan" dan tidak dengan cara apa pun terlibat dalam kepemilikan, baik melalui penciptaan langsung atau secara implisit melalui nilai pengembalian. Lihat juga Tanya Jawab ini dari C ++ FAQ .

Kontrol seumur hidup yang lebih halus Setiap kali pointer bersama disalin (misalnya sebagai argumen fungsi) sumber daya yang ditunjukkannya tetap hidup. Objek reguler (tidak dibuat oleh new, baik secara langsung oleh Anda atau di dalam kelas sumber daya) dihancurkan ketika keluar dari ruang lingkup.

TemplateRex
sumber
17
"Membuat pointer ke sumber daya menggunakan operator baru adalah anti-pola" Saya pikir Anda bahkan dapat meningkatkan bahwa memiliki pointer mentah sendiri sesuatu adalah anti-pola . Tidak hanya pembuatan, tetapi melewatkan pointer mentah sebagai argumen atau nilai pengembalian yang menyiratkan transfer kepemilikan IMHO sudah tidak digunakan lagi sejak unique_ptr/ pindah semantik
dyp
1
@dt tnx, dimutakhirkan dan merujuk ke Tanya Jawab C ++ tentang topik ini.
TemplateRex
4
Menggunakan pointer pintar di mana-mana adalah anti-pola. Ada beberapa kasus khusus di mana itu berlaku, tetapi sebagian besar waktu, alasan yang sama yang berpendapat untuk alokasi dinamis (seumur hidup sewenang-wenang) berdebat terhadap salah satu pointer cerdas biasa juga.
James Kanze
2
@JamesKanze Saya tidak bermaksud mengatakan bahwa smart pointer harus digunakan di mana-mana, hanya untuk kepemilikan, dan juga bahwa pointer mentah tidak boleh digunakan untuk kepemilikan, tetapi hanya untuk pandangan.
TemplateRex
2
@TemplateRex Tampaknya agak konyol mengingat itu hun(b)juga membutuhkan pengetahuan tentang tanda tangan kecuali Anda baik-baik saja dengan tidak tahu bahwa Anda memasukkan jenis yang salah sampai kompilasi. Meskipun masalah referensi biasanya tidak akan tertangkap pada waktu kompilasi dan akan membutuhkan lebih banyak upaya untuk debug, jika Anda memeriksa tanda tangan untuk memastikan argumennya benar, Anda juga akan dapat melihat apakah ada argumen yang menjadi referensi jadi bit referensi menjadi sesuatu yang bukan masalah (terutama ketika menggunakan IDE atau editor teks yang menunjukkan tanda tangan dari fungsi yang dipilih). Juga const&,.
JAB
130

Ada banyak jawaban bagus untuk pertanyaan ini, termasuk kasus penggunaan penting deklarasi maju, polimorfisme dll. Tapi saya merasa bagian dari "jiwa" pertanyaan Anda tidak dijawab - yaitu apa arti sintaks yang berbeda di seluruh Jawa dan C ++.

Mari kita periksa situasi membandingkan kedua bahasa:

Jawa:

Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java

object1 = object2; 
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other

Setara terdekat dengan ini, adalah:

C ++:

Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete object1;
//Since C++ does not have a garbage collector, if we don't do that, the next line would 
//cause a "memory leak", i.e. a piece of claimed memory that the app cannot use 
//and that we have no way to reclaim...

object1 = object2; //Same as Java, object1 points to object2.

Mari kita lihat cara C ++ alternatif:

Object object1; //A new object is allocated on the STACK
Object object2; //Another new object is allocated on the STACK
object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
//using the "copy assignment operator", the definition of operator =.
//But, the two objects are still different. Change one, the other remains unchanged.
//Also, the objects get automatically destroyed once the function returns...

Cara terbaik untuk memikirkannya adalah bahwa - lebih atau kurang - Java (secara implisit) menangani pointer ke objek, sedangkan C ++ dapat menangani pointer ke objek, atau objek itu sendiri. Ada pengecualian untuk ini - misalnya, jika Anda mendeklarasikan tipe Java "primitif", mereka adalah nilai aktual yang disalin, dan bukan pointer. Begitu,

Jawa:

int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.

Yang mengatakan, menggunakan pointer TIDAK selalu benar atau cara yang salah untuk menangani sesuatu; Namun jawaban lain telah mencakup hal itu dengan memuaskan. Namun gagasan umum adalah bahwa dalam C ++ Anda memiliki kontrol lebih besar pada masa hidup objek, dan di mana mereka akan hidup.

Ambil titik asal - Object * object = new Object()konstruknya adalah semantik yang paling dekat dengan tipikal Java (atau C #).

Gerasimos R
sumber
7
Object2 is now "dead": Saya pikir maksud Anda myObject1atau lebih tepatnya the object pointed to by myObject1.
Clément
2
Memang! Diulang sedikit.
Gerasimos R
2
Object object1 = new Object(); Object object2 = new Object();adalah kode yang sangat buruk. Baru atau kedua Obyek konstruktor kedua dapat melempar, dan sekarang objek1 bocor. Jika Anda menggunakan news mentah , Anda harus membungkus newobjek ed dalam pembungkus RAII secepatnya.
PSkocik
8
Memang, itu akan terjadi jika ini adalah sebuah program, dan tidak ada lagi yang terjadi di sekitarnya. Untungnya, ini hanya cuplikan penjelasan yang menunjukkan bagaimana Pointer di C ++ berperilaku - dan salah satu dari sedikit tempat di mana objek RAII tidak dapat diganti dengan pointer mentah, sedang belajar dan belajar tentang pointer mentah ...
Gerasimos R
80

Alasan bagus lainnya untuk menggunakan pointer adalah untuk deklarasi maju . Dalam proyek yang cukup besar mereka benar-benar dapat mempercepat waktu kompilasi.

Roti Bakar
sumber
7
ini benar-benar menambah campuran informasi yang berguna, sangat senang Anda membuat jawabannya!
TemplateRex
3
std :: shared_ptr <T> juga bekerja dengan deklarasi maju dari T. (std :: unique_ptr <T> tidak )
berkus
13
@berkus: std::unique_ptr<T>berfungsi dengan deklarasi maju dari T. Anda hanya perlu memastikan bahwa ketika destructor dari std::unique_ptr<T>disebut, Tadalah tipe yang lengkap. Ini biasanya berarti kelas Anda yang berisi std::unique_ptr<T>mendeklarasikan destruktornya di file header dan mengimplementasikannya dalam file cpp (bahkan jika implementasinya kosong).
David Stone
Apakah modul akan memperbaikinya?
Trevor Hickey
@TrevorHickey Komentar lama saya tahu, tetapi untuk menjawabnya. Modul tidak akan menghapus dependensi, tetapi membuat dependensi menjadi sangat murah, hampir gratis dalam hal biaya kinerja. Juga, jika speedup umum dari modul akan cukup untuk mendapatkan waktu kompilasi Anda dalam rentang yang dapat diterima, itu tidak lagi menjadi masalah.
Aidiakapi
79

Kata pengantar

Java tidak seperti C ++, bertentangan dengan hype. Mesin Java hype ingin Anda percaya bahwa karena Java memiliki sintaksis C ++, maka bahasanya mirip. Tidak ada yang bisa lebih jauh dari kebenaran. Informasi yang salah ini adalah bagian dari alasan mengapa programmer Java pergi ke C ++ dan menggunakan sintaks seperti Java tanpa memahami implikasi dari kode mereka.

Selanjutnya kita pergi

Tetapi saya tidak tahu mengapa kita harus melakukannya dengan cara ini. Saya akan menganggap itu ada hubungannya dengan efisiensi dan kecepatan karena kita mendapatkan akses langsung ke alamat memori. Apakah saya benar?

Sebaliknya, sebenarnya. Tumpukan jauh lebih lambat daripada tumpukan, karena tumpukan sangat sederhana dibandingkan dengan tumpukan. Variabel penyimpanan otomatis (alias tumpukan variabel) memiliki destruktor mereka dipanggil begitu mereka keluar dari ruang lingkup. Sebagai contoh:

{
    std::string s;
}
// s is destroyed here

Di sisi lain, jika Anda menggunakan pointer yang dialokasikan secara dinamis, penghancurnya harus dipanggil secara manual. deletememanggil destruktor ini untuk Anda.

{
    std::string* s = new std::string;
}
delete s; // destructor called

Ini tidak ada hubungannya dengan new sintaks yang lazim di C # dan Java. Mereka digunakan untuk tujuan yang sama sekali berbeda.

Manfaat alokasi dinamis

1. Anda tidak perlu tahu ukuran array terlebih dahulu

Salah satu masalah pertama yang dialami oleh banyak programmer C ++ adalah ketika mereka menerima input sewenang-wenang dari pengguna, Anda hanya dapat mengalokasikan ukuran tetap untuk variabel stack. Anda juga tidak dapat mengubah ukuran array. Sebagai contoh:

char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow

Tentu saja, jika Anda menggunakan std::stringgantinya, secara std::stringinternal mengubah ukuran sendiri sehingga seharusnya tidak menjadi masalah. Tetapi pada dasarnya solusi untuk masalah ini adalah alokasi dinamis. Anda dapat mengalokasikan memori dinamis berdasarkan input pengguna, misalnya:

int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];

Catatan : Satu kesalahan yang banyak dilakukan oleh pemula adalah penggunaan array panjang variabel. Ini adalah ekstensi GNU dan juga satu di Dentang karena mereka mencerminkan banyak ekstensi GCC. Jadi yang berikut ini int arr[n]jangan dijadikan andalan.

Karena tumpukan jauh lebih besar dari tumpukan, seseorang dapat secara sewenang-wenang mengalokasikan / mengalokasikan kembali memori sebanyak yang dia butuhkan, sedangkan tumpukan memiliki batasan.

2. Array bukan pointer

Bagaimana ini manfaat yang Anda minta? Jawabannya akan menjadi jelas setelah Anda memahami kebingungan / mitos di balik array dan pointer. Secara umum diasumsikan bahwa mereka sama, tetapi mereka tidak sama. Mitos ini berasal dari fakta bahwa pointer dapat disalin seperti halnya array dan karena peluruhan array ke pointer di tingkat atas dalam deklarasi fungsi. Namun, begitu sebuah array meluruh menjadi sebuah pointer, pointer kehilangan sizeofinformasinya. Begitusizeof(pointer) akan memberikan ukuran pointer dalam byte, yang biasanya 8 byte pada sistem 64-bit.

Anda tidak dapat menetapkan untuk array, hanya menginisialisasi mereka. Sebagai contoh:

int arr[5] = {1, 2, 3, 4, 5}; // initialization 
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
                             // be given by the amount of members in the initializer  
arr = { 1, 2, 3, 4, 5 }; // ERROR

Di sisi lain, Anda dapat melakukan apa pun yang Anda inginkan dengan pointer. Sayangnya, karena perbedaan antara pointer dan array adalah lambaian tangan di Jawa dan C #, pemula tidak mengerti perbedaannya.

3. Polimorfisme

Java dan C # memiliki fasilitas yang memungkinkan Anda untuk memperlakukan objek sebagai objek lain, misalnya menggunakan askata kunci. Jadi, jika seseorang ingin memperlakukan Entityobjek sebagai Playerobjek, orang dapat melakukannya. Player player = Entity as Player;Ini sangat berguna jika Anda bermaksud memanggil fungsi pada wadah yang homogen yang seharusnya hanya berlaku untuk jenis tertentu. Fungsionalitas dapat dicapai dengan cara serupa di bawah ini:

std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
     auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
     if (!test) // not a triangle
        e.GenericFunction();
     else
        e.TriangleOnlyMagic();
}

Jadi katakanlah jika hanya Segitiga yang memiliki fungsi Putar, itu akan menjadi kesalahan kompiler jika Anda mencoba menyebutnya pada semua objek kelas. Dengan menggunakan dynamic_cast, Anda dapat mensimulasikan askata kunci. Untuk menjadi jelas, jika gips gagal, itu mengembalikan pointer yang tidak valid. Jadi !testpada dasarnya adalah singkatan untuk memeriksa apakahtest NULL atau pointer tidak valid, yang berarti para pemain gagal.

Manfaat variabel otomatis

Setelah melihat semua hal hebat yang dapat dilakukan alokasi dinamis, Anda mungkin bertanya-tanya mengapa tidak ada yang TIDAK menggunakan alokasi dinamis sepanjang waktu? Saya sudah memberi tahu Anda satu alasan, tumpukannya lambat. Dan jika Anda tidak membutuhkan semua memori itu, Anda tidak harus menyalahgunakannya. Jadi, inilah beberapa kelemahan tanpa urutan tertentu:

  • Ini rawan kesalahan. Alokasi memori manual berbahaya dan Anda cenderung bocor. Jika Anda tidak mahir menggunakan debugger atau valgrind(alat kebocoran memori), Anda dapat menarik rambut keluar dari kepala. Untungnya idiom dan petunjuk pintar RAII meringankan ini sedikit, tetapi Anda harus terbiasa dengan praktik seperti Aturan Tiga dan Aturan Lima. Ini adalah banyak informasi untuk diambil, dan pemula yang entah tidak tahu atau tidak peduli akan jatuh ke dalam perangkap ini.

  • Itu tidak perlu. Tidak seperti Java dan C # yang menggunakan newkata kunci di mana-mana, di C ++, Anda hanya boleh menggunakannya jika perlu. Ungkapan umum berbunyi, semuanya tampak seperti paku jika Anda memiliki palu. Sedangkan pemula yang mulai dengan C ++ takut pointer dan belajar menggunakan variabel stack berdasarkan kebiasaan, Java dan programmer C # memulai dengan menggunakan pointer tanpa memahaminya! Itu benar-benar menginjak kaki yang salah. Anda harus meninggalkan semua yang Anda tahu karena sintaks adalah satu hal, belajar bahasa adalah hal lain.

1. (N) RVO - Aka, (Bernama) Optimasi Nilai Kembali

Salah satu optimisasi yang dilakukan oleh banyak kompiler adalah hal-hal yang disebut elision dan optimasi nilai pengembalian . Hal-hal ini dapat menghindarkan copys yang tidak perlu yang berguna untuk objek yang sangat besar, seperti vektor yang mengandung banyak elemen. Biasanya praktik umum adalah menggunakan pointer untuk mentransfer kepemilikan daripada menyalin objek besar untuk memindahkannya . Ini telah mengarah pada awal semantik langkah dan pointer pintar .

Jika Anda menggunakan pointer, (N) RVO TIDAK terjadi. Lebih menguntungkan dan lebih rentan kesalahan untuk memanfaatkan (N) RVO daripada mengembalikan atau melewati pointer jika Anda khawatir tentang pengoptimalan. Kebocoran kesalahan dapat terjadi jika pemanggil suatu fungsi bertanggung jawab atas deleteobjek yang dialokasikan secara dinamis dan semacamnya. Mungkin sulit untuk melacak kepemilikan suatu objek jika pointer dibagikan seperti kentang panas. Cukup gunakan variabel tumpukan karena lebih sederhana dan lebih baik.

pengguna3391320
sumber
"Jadi! Tes pada dasarnya adalah singkatan untuk memeriksa apakah tes NULL atau pointer tidak valid, yang berarti para pemain gagal." Saya pikir kalimat ini harus ditulis ulang agar lebih jelas.
berkus
4
"Mesin hype Java ingin Anda percaya" - mungkin pada tahun 1997, tetapi ini sekarang ketinggalan zaman, tidak ada lagi motivasi untuk membandingkan Java dengan C ++ pada tahun 2014.
Matt R
15
Pertanyaan lama, tetapi di segmen kode { std::string* s = new std::string; } delete s; // destructor called.... pasti ini deletetidak akan berhasil karena kompiler tidak akan tahu apa slagi?
badger5000
2
Saya TIDAK memberi -1, tetapi saya tidak setuju dengan pernyataan pembukaan seperti yang tertulis. Pertama, saya tidak setuju ada "hype" - mungkin sekitar Y2K, tapi sekarang kedua bahasa dipahami dengan baik. Kedua, saya berpendapat bahwa mereka sangat mirip - C + + adalah anak dari C menikah dengan Simula, Java menambahkan Mesin Virtual, Pengumpul Sampah dan HEAVILY mengurangi fitur, dan C # merampingkan dan memperkenalkan kembali fitur yang hilang ke Jawa. Ya, ini membuat pola dan penggunaan yang valid HUGELY berbeda, tetapi bermanfaat untuk memahami infrastruktur umum / desing sehingga orang dapat melihat perbedaan.
Gerasimos R
1
@ James Matta: Anda tentu saja benar bahwa memori adalah memori, dan keduanya dialokasikan dari memori fisik yang sama, tetapi satu hal yang perlu dipertimbangkan adalah bahwa sangat umum untuk mendapatkan karakteristik kinerja yang lebih baik bekerja dengan objek yang dialokasikan stack karena stack - atau setidaknya level tertingginya - memiliki peluang sangat tinggi untuk menjadi "panas" dalam cache saat fungsi masuk dan keluar, sementara heap tidak memiliki manfaat seperti itu jadi jika Anda mengejar pointer di heap Anda mungkin mendapatkan beberapa cache yang meleset yang Anda kemungkinan tidak akan berada di tumpukan. Tetapi semua "keacakan" ini biasanya mendukung tumpukan.
Gerasimos R
23

C ++ memberi Anda tiga cara untuk melewatkan objek: dengan pointer, dengan referensi, dan dengan nilai. Java membatasi Anda dengan yang terakhir (satu-satunya pengecualian adalah tipe primitif seperti int, boolean dll). Jika Anda ingin menggunakan C ++ bukan hanya seperti mainan aneh, maka Anda sebaiknya mengetahui perbedaan antara tiga cara ini.

Jawa berpura-pura bahwa tidak ada masalah seperti 'siapa dan kapan harus menghancurkan ini?'. Jawabannya adalah: Pengumpul Sampah, Hebat dan Mengerikan. Namun demikian, itu tidak dapat memberikan perlindungan 100% terhadap kebocoran memori (ya, java dapat membocorkan memori ). Sebenarnya, GC memberi Anda rasa aman yang salah. Semakin besar SUV Anda, semakin lama Anda menuju evacuator.

C ++ membuat Anda berhadapan muka dengan manajemen siklus hidup objek. Nah, ada cara untuk menghadapinya ( keluarga smart pointer , QObject di Qt dan sebagainya), tetapi tidak satu pun dari mereka dapat digunakan dengan cara 'api dan lupakan' seperti GC: Anda harus selalu mengingat penanganan memori. Anda tidak hanya harus peduli menghancurkan objek, Anda juga harus menghindari menghancurkan objek yang sama lebih dari satu kali.

Belum takut? Ok: referensi siklik - tangani sendiri, manusia. Dan ingat: bunuh setiap objek tepat sekali, kita runtime C ++ tidak suka mereka yang mengacaukan mayat, biarkan yang mati sendirian.

Jadi, kembali ke pertanyaan Anda.

Ketika Anda melewati objek Anda berdasarkan nilai, bukan dengan pointer atau dengan referensi, Anda menyalin objek (seluruh objek, apakah itu beberapa byte atau dump database besar - Anda cukup pintar untuk peduli untuk menghindari yang terakhir, bukan? t you?) setiap kali Anda melakukan '='. Dan untuk mengakses anggota objek, Anda menggunakan '.' (dot).

Ketika Anda melewati objek Anda dengan pointer, Anda hanya menyalin beberapa byte (4 pada sistem 32-bit, 8 pada yang 64-bit), yaitu - alamat objek ini. Dan untuk menunjukkan ini kepada semua orang, Anda menggunakan operator '->' mewah ini ketika Anda mengakses anggota. Atau Anda dapat menggunakan kombinasi '*' dan '.'.

Saat Anda menggunakan referensi, maka Anda mendapatkan pointer yang berpura-pura menjadi nilai. Ini adalah pointer, tetapi Anda mengakses anggota melalui '.'

Dan, untuk meniup pikiran Anda sekali lagi: ketika Anda mendeklarasikan beberapa variabel yang dipisahkan oleh koma, maka (perhatikan tangan):

  • Jenis diberikan kepada semua orang
  • Pengubah nilai / penunjuk / referensi bersifat individual

Contoh:

struct MyStruct
{
    int* someIntPointer, someInt; //here comes the surprise
    MyStruct *somePointer;
    MyStruct &someReference;
};

MyStruct s1; //we allocated an object on stack, not in heap

s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
s1.someIntPointer = &s1.someInt;
*s1.someIntPointer = 2; //now s1.someInt has value '2'
s1.somePointer = &s1;
s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
s1.somePointer->someInt = 3; //now s1.someInt has value '3'
*(s1.somePointer).someInt = 3; //same as above line
*s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'

s1.someReference.someInt = 5; //now s1.someInt has value '5'
                              //although someReference is not value, it's members are accessed through '.'

MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.

//OK, assume we have '=' defined in MyStruct

s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one
Kirill Gamazkov
sumber
1
std::auto_ptrsudah usang, tolong jangan gunakan itu.
Neil
2
Cukup yakin Anda tidak dapat memiliki referensi sebagai anggota tanpa juga menyediakan konstruktor dengan daftar inisialisasi yang mencakup variabel referensi. (Referensi harus segera diinisialisasi. Bahkan badan konstruktor terlambat untuk mengaturnya, IIRC.)
cHao
20

Dalam C ++, objek yang dialokasikan pada stack (menggunakan Object object;pernyataan dalam blok) hanya akan hidup dalam lingkup yang dideklarasikan. Ketika blok kode selesai dieksekusi, objek yang dideklarasikan dihancurkan. Sedangkan jika Anda mengalokasikan memori pada tumpukan, menggunakan Object* obj = new Object(), mereka terus hidup di tumpukan sampai Anda menelepon delete obj.

Saya akan membuat objek pada heap ketika saya ingin menggunakan objek tidak hanya dalam blok kode yang menyatakan / mengalokasikannya.

Karthik Kalyanasundaram
sumber
6
Object objtidak selalu ada di tumpukan - misalnya variabel global atau anggota.
Tenfour
2
@LightnessRacesinOrbit Saya hanya menyebutkan tentang objek yang dialokasikan dalam sebuah blok, bukan tentang variabel global dan anggota. Masalahnya itu tidak jelas, sekarang diperbaiki - ditambahkan "dalam blok" dalam jawabannya. Semoga ini bukan informasi yang salah sekarang :)
Karthik Kalyanasundaram
20

Tapi saya tidak tahu mengapa kita harus menggunakannya seperti ini?

Saya akan membandingkan cara kerjanya di dalam fungsi tubuh jika Anda menggunakan:

Object myObject;

Di dalam fungsi, Anda myObject akan hancur setelah fungsi ini kembali. Jadi ini berguna jika Anda tidak memerlukan objek di luar fungsi Anda. Objek ini akan diletakkan di tumpukan thread saat ini.

Jika Anda menulis di dalam fungsi tubuh:

 Object *myObject = new Object;

kemudian instance kelas Object yang ditunjuk oleh myObjecttidak akan hancur setelah fungsi berakhir, dan alokasi ada di heap.

Sekarang jika Anda adalah programmer Java, maka contoh kedua lebih dekat dengan bagaimana alokasi objek bekerja di bawah java. Baris ini: Object *myObject = new Object;setara dengan java: Object myObject = new Object();. Perbedaannya adalah bahwa di bawah java myObject akan mengumpulkan sampah, sedangkan di bawah c ++ tidak akan dibebaskan, Anda harus secara eksplisit memanggil 'delete myObject;' jika tidak, Anda akan mengalami kebocoran memori.

Sejak c ++ 11 Anda dapat menggunakan cara aman dari alokasi dinamis:, new Objectdengan menyimpan nilai dalam shared_ptr / unique_ptr.

std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");

// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared"); 

juga, objek sangat sering disimpan dalam wadah, seperti map-s atau vektor-s, mereka akan secara otomatis mengatur seumur hidup objek Anda.

marcinj
sumber
1
then myObject will not get destroyed once function endsItu benar-benar akan.
Lightness Races dalam Orbit
6
Dalam kasus pointer, myObjectmasih akan dihancurkan, sama seperti variabel lokal lainnya akan. Perbedaannya adalah bahwa nilainya adalah penunjuk ke objek, bukan objek itu sendiri, dan penghancuran penunjuk bodoh tidak memengaruhi titiknya. Jadi benda itu akan selamat dari kehancuran.
cao
Tetap itu, variabel lokal (yang termasuk pointer) tentu saja akan dibebaskan - mereka ada di stack.
marcinj
13

Secara teknis ini adalah masalah alokasi memori, namun di sini ada dua aspek yang lebih praktis dari ini. Ini ada hubungannya dengan dua hal: 1) Lingkup, ketika Anda mendefinisikan suatu objek tanpa pointer Anda tidak akan lagi dapat mengaksesnya setelah blok kode itu didefinisikan, sedangkan jika Anda mendefinisikan pointer dengan "baru" maka Anda dapat mengaksesnya dari mana saja Anda memiliki pointer ke memori ini sampai Anda memanggil "delete" pada pointer yang sama. 2) Jika Anda ingin meneruskan argumen ke suatu fungsi, Anda ingin meneruskan sebuah pointer atau referensi agar lebih efisien. Ketika Anda melewati Obyek maka objek tersebut disalin, jika ini adalah objek yang menggunakan banyak memori, ini mungkin memakan CPU (misalnya Anda menyalin vektor penuh data). Ketika Anda melewati pointer semua yang Anda lulus adalah satu int (tergantung dari implementasi tetapi kebanyakan dari mereka adalah satu int).

Selain itu, Anda perlu memahami bahwa "baru" mengalokasikan memori pada heap yang perlu dibebaskan di beberapa titik. Ketika Anda tidak harus menggunakan "baru", saya sarankan Anda menggunakan definisi objek biasa "pada tumpukan".

membutuhkan bantuan
sumber
6

Nah pertanyaan utamanya adalah mengapa saya harus menggunakan pointer daripada objek itu sendiri? Dan jawaban saya, Anda seharusnya (hampir) tidak pernah menggunakan pointer daripada objek, karena C ++ memiliki referensi , lebih aman daripada pointer dan menjamin kinerja yang sama dengan pointer.

Hal lain yang Anda sebutkan dalam pertanyaan Anda:

Object *myObject = new Object;

Bagaimana cara kerjanya? Ini menciptakan pointer Objecttipe, mengalokasikan memori agar sesuai dengan satu objek dan memanggil konstruktor default, kedengarannya bagus, kan? Tetapi sebenarnya itu tidak begitu baik, jika Anda mengalokasikan memori secara dinamis (kata kunci yang digunakan new), Anda juga harus membebaskan memori secara manual, itu berarti dalam kode Anda harus memiliki:

delete myObject;

Ini panggilan destruktor dan membebaskan memori, terlihat mudah, namun dalam proyek-proyek besar mungkin sulit untuk mendeteksi apakah satu utas membebaskan memori atau tidak, tetapi untuk tujuan itu Anda dapat mencoba berbagi petunjuk , ini sedikit menurunkan kinerja, tetapi jauh lebih mudah untuk bekerja dengan mereka.


Dan sekarang beberapa pengantar telah selesai dan kembali ke pertanyaan.

Anda dapat menggunakan pointer alih-alih objek untuk mendapatkan kinerja yang lebih baik saat mentransfer data antar fungsi.

Lihatlah, Anda telah std::string(itu juga objek) dan itu berisi sangat banyak data, misalnya XML besar, sekarang Anda perlu menguraikannya, tetapi untuk itu Anda memiliki fungsi void foo(...)yang dapat dideklarasikan dengan cara yang berbeda:

  1. void foo(std::string xml); Dalam hal ini Anda akan menyalin semua data dari variabel Anda ke tumpukan fungsi, itu membutuhkan waktu, sehingga kinerja Anda akan rendah.
  2. void foo(std::string* xml); Dalam hal ini Anda akan melewatkan pointer ke objek, kecepatan yang sama seperti size_tvariabel yang lewat , namun deklarasi ini memiliki kecenderungan kesalahan, karena Anda dapat melewati NULLpointer atau pointer yang tidak valid. Pointer biasanya digunakan Ckarena tidak memiliki referensi.
  3. void foo(std::string& xml); Di sini Anda melewati referensi, pada dasarnya sama dengan melewati pointer, tetapi kompiler melakukan beberapa hal dan Anda tidak dapat melewati referensi yang tidak valid (sebenarnya dimungkinkan untuk membuat situasi dengan referensi yang tidak valid, tetapi itu menipu kompiler).
  4. void foo(const std::string* xml); Ini sama dengan yang kedua, hanya nilai pointer tidak bisa diubah.
  5. void foo(const std::string& xml); Di sini sama dengan ketiga, tetapi nilai objek tidak dapat diubah.

Apa lagi yang ingin saya sebutkan, Anda dapat menggunakan 5 cara ini untuk meneruskan data apa pun cara alokasi yang telah Anda pilih (dengan newatau reguler ).


Satu hal lagi, ketika Anda membuat objek dengan cara biasa , Anda mengalokasikan memori dalam tumpukan, tetapi saat Anda membuatnya dengan newAnda mengalokasikan tumpukan. Jauh lebih cepat untuk mengalokasikan stack, tetapi ini agak kecil untuk array data yang sangat besar, jadi jika Anda membutuhkan objek besar Anda harus menggunakan heap, karena Anda mungkin mendapatkan stack overflow, tetapi biasanya masalah ini diselesaikan menggunakan wadah STL dan ingat std::stringjuga wadah, beberapa orang lupa :)

ST3
sumber
5

Mari kita mengatakan bahwa Anda memiliki class Ayang mengandung class BBila Anda ingin memanggil beberapa fungsi class Bdi luar class AAnda hanya akan mendapatkan pointer ke kelas ini dan Anda dapat melakukan apapun yang Anda inginkan dan itu juga akan mengubah konteks class Bdi Andaclass A

Tapi hati-hati dengan objek yang dinamis

Pencarian
sumber
5

Ada banyak manfaat menggunakan pointer ke objek -

  1. Efisiensi (seperti yang sudah Anda tunjukkan). Melewati objek ke fungsi berarti membuat salinan objek baru.
  2. Bekerja dengan objek dari pustaka pihak ketiga. Jika objek Anda milik kode pihak ketiga dan penulis bermaksud menggunakan objek mereka melalui pointer saja (tidak ada copy konstruktor dll) satu-satunya cara Anda bisa melewati objek ini menggunakan pointer. Melewati nilai dapat menyebabkan masalah. (Masalah salinan dalam / salinan dangkal).
  3. jika objek memiliki sumber daya dan Anda ingin kepemilikannya tidak sah dengan objek lain.
Rohit
sumber
3

Ini telah dibahas panjang lebar, tetapi di Jawa semuanya adalah pointer. Itu tidak membuat perbedaan antara alokasi stack dan heap (semua objek dialokasikan pada heap), jadi Anda tidak menyadari bahwa Anda menggunakan pointer. Di C ++, Anda dapat mencampur keduanya, tergantung pada kebutuhan memori Anda. Kinerja dan penggunaan memori lebih deterministik dalam C ++ (duh).

cmollis
sumber
3
Object *myObject = new Object;

Melakukan ini akan membuat referensi ke Obyek (di heap) yang harus dihapus secara eksplisit untuk menghindari kebocoran memori .

Object myObject;

Melakukan ini akan membuat objek (myObject) dari tipe otomatis (pada stack) yang akan dihapus secara otomatis ketika objek (myObject) keluar dari ruang lingkup.

Palak Jain
sumber
1

Pointer secara langsung merujuk lokasi memori suatu objek. Java tidak memiliki yang seperti ini. Java memiliki referensi yang merujuk lokasi objek melalui tabel hash. Anda tidak dapat melakukan hal seperti pointer aritmatika di Jawa dengan referensi ini.

Untuk menjawab pertanyaan Anda, itu hanya preferensi Anda. Saya lebih suka menggunakan sintaks mirip Java.

RioRicoRick
sumber
Meja hash? Mungkin di beberapa JVM tapi jangan mengandalkannya.
Zan Lynx
Bagaimana dengan JVM yang datang dengan Java? Tentu saja Anda dapat menerapkan APA SAJA yang dapat Anda pikirkan seperti JVM yang menggunakan pointer secara langsung atau metode yang melakukan pointer matematika. Itu seperti mengatakan "orang tidak mati karena flu biasa" dan mendapat jawaban "Mungkin kebanyakan orang tidak, tetapi jangan mengandalkannya!" Ha ha.
RioRicoRick
2
@RioRicoRick HotSpot mengimplementasikan referensi Java sebagai pointer asli, lihat docs.oracle.com/javase/7/docs/technotes/guides/vm/… Sejauh yang saya lihat, JRockit melakukan hal yang sama. Keduanya mendukung kompresi OOP, tetapi tidak pernah menggunakan tabel hash. Konsekuensi kinerja mungkin akan menjadi bencana. Juga, "itu hanya preferensi Anda" tampaknya menyiratkan bahwa keduanya hanyalah sintaks yang berbeda untuk perilaku yang setara, yang tentu saja tidak.
Max Barraclough
0

Dengan pointer ,

  • dapat langsung berbicara ke memori.

  • dapat mencegah banyak kebocoran memori suatu program dengan memanipulasi pointer.

lasan
sumber
4
" di C ++, menggunakan pointer, Anda dapat membuat pengumpul sampah khusus untuk program Anda sendiri " yang terdengar seperti ide yang buruk.
quant
0

Salah satu alasan untuk menggunakan pointer adalah untuk berinteraksi dengan fungsi C. Alasan lain adalah untuk menghemat memori; misalnya: alih-alih meneruskan objek yang berisi banyak data dan memiliki prosesor-konstruktor intensif untuk suatu fungsi, cukup berikan pointer ke objek tersebut, hemat memori dan kecepatan terutama jika Anda berada dalam satu lingkaran, namun referensi akan lebih baik dalam hal itu, kecuali jika Anda menggunakan array gaya-C.


sumber
0

Di daerah-daerah di mana pemanfaatan memori berada pada puncaknya, petunjuk berguna. Sebagai contoh, pertimbangkan algoritma minimax, di mana ribuan node akan dihasilkan menggunakan rutin rekursif, dan kemudian menggunakannya untuk mengevaluasi langkah terbaik berikutnya dalam game, kemampuan untuk melakukan deallocate atau reset (seperti pada smart pointer) secara signifikan mengurangi konsumsi memori. Sedangkan variabel non-pointer terus menempati ruang hingga panggilan rekursifnya mengembalikan nilai.

seccpur
sumber
0

Saya akan menyertakan satu kasus penggunaan pointer yang penting. Ketika Anda menyimpan beberapa objek di kelas dasar, tetapi bisa jadi polimorfik.

Class Base1 {
};

Class Derived1 : public Base1 {
};


Class Base2 {
  Base *bObj;
  virtual void createMemerObects() = 0;
};

Class Derived2 {
  virtual void createMemerObects() {
    bObj = new Derived1();
  }
};

Jadi dalam hal ini Anda tidak dapat mendeklarasikan bObj sebagai objek langsung, Anda harus memiliki pointer.

pengguna18853
sumber
-5

"Kebutuhan adalah ibu dari penemuan." Perbedaan paling penting yang ingin saya tunjukkan adalah hasil dari pengalaman saya sendiri dalam pengkodean. Terkadang Anda perlu meneruskan objek ke fungsi. Dalam hal ini, jika objek Anda adalah kelas yang sangat besar maka melewatinya sebagai objek akan menyalin keadaannya (yang mungkin tidak Anda inginkan .. DAN BISA MENJADI LEBIH BANYAK KE LUAR) sehingga menghasilkan overhead menyalin objek. Sementara pointer diperbaiki Ukuran 4-byte (dengan asumsi 32 bit). Alasan lain sudah disebutkan di atas ...

sandeep bisht
sumber
14
Anda sebaiknya memilih lewat referensi
bolov
2
Saya sarankan meneruskan dengan referensi konstan seperti untuk variabel yang std::string test;kita miliki void func(const std::string &) {}tetapi kecuali fungsi perlu mengubah input dalam hal ini saya sarankan menggunakan pointer (sehingga siapa pun yang membaca kode tidak melihat &, dan memahami fungsi dapat mengubah inputnya)
Top- Master
-7

Sudah ada banyak jawaban bagus, tetapi izinkan saya memberi Anda satu contoh:

Saya memiliki kelas Item sederhana:

 class Item
    {
    public: 
      std::string name;
      int weight;
      int price;
    };

Saya membuat vektor untuk menampung banyak dari mereka.

std::vector<Item> inventory;

Saya membuat satu juta objek Item, dan mendorongnya kembali ke vektor. Saya mengurutkan vektor berdasarkan nama, dan kemudian melakukan pencarian biner iteratif sederhana untuk nama item tertentu. Saya menguji program, dan dibutuhkan lebih dari 8 menit untuk menyelesaikan eksekusi. Lalu saya mengubah vektor inventaris saya seperti:

std::vector<Item *> inventory;

... dan buat sejuta objek Item saya melalui yang baru. HANYA perubahan yang saya buat pada kode saya adalah dengan menggunakan pointer ke Items, kecuali sebuah loop yang saya tambahkan untuk pembersihan memori pada akhirnya. Program itu berjalan di bawah 40 detik, atau lebih baik dari peningkatan kecepatan 10x. EDIT: Kode ini di http://pastebin.com/DK24SPeW Dengan optimisasi kompiler hanya menunjukkan peningkatan 3,4x pada mesin saya baru saja mengujinya, yang masih cukup besar.

Darren
sumber
2
Nah apakah Anda membandingkan pointer itu atau apakah Anda masih membandingkan objek yang sebenarnya? Saya sangat meragukan bahwa tingkat tipuan yang lain dapat meningkatkan kinerja. Berikan kode! Apakah Anda membersihkan dengan benar setelah itu?
Stefan
1
@stefan saya membandingkan data (khususnya, bidang nama) objek untuk kedua jenis dan pencarian. Saya membersihkan dengan benar, seperti yang saya sebutkan di pos. speedup mungkin karena dua faktor: 1) std :: vector push_back () menyalin objek, jadi versi pointer hanya perlu menyalin satu pointer per objek. Ini memiliki beberapa dampak pada kinerja, karena tidak hanya lebih sedikit data yang disalin, tetapi pengalokasi memori kelas vektor juga berkurang.
Darren
2
Berikut ini kode yang menunjukkan hampir tidak ada perbedaan untuk contoh Anda: pengurutan. Kode penunjuk adalah 6% lebih cepat daripada kode bukan penunjuk untuk pengurutan saja, tetapi secara keseluruhan 10% lebih lambat daripada kode bukan-penunjuk. ideone.com/G0c7zw
stefan
3
Kata kunci: push_back. Tentu saja salinan ini. Anda seharusnya berada emplacedi tempat ketika membuat objek Anda (kecuali Anda membutuhkannya untuk di-cache di tempat lain).
underscore_d
1
Vektor pointer hampir selalu salah. Tolong jangan merekomendasikan mereka tanpa menjelaskan, secara rinci, peringatan dan pro dan kontra. Anda tampaknya telah menemukan satu pro, yang hanya merupakan konsekuensi dari contoh tandingan yang buruk, dan salah mengartikannya
Lightness Races in Orbit