Mengapa konstruktor std :: unique_ptr menerima pointer eksternal?

Sep 03 2020

Saya relatif baru mengenal cpp, dan sedang mempelajari tentang poin pintar. Saya bertanya-tanya yang berikut:

Mengapa membangun std::unique_ptrdengan an lvaluediperbolehkan?

Bukankah lebih aman jika hanya mengizinkan pembangunan std::unique_ptrdengan nilai r untuk menghindari hal-hal yang jahat?

std::unique_ptr<int> createInt() {
    int* a = new int(99);
    std::unique_ptr<int> foo(a);
    delete a;
    return foo;
}

Saya menyadari Anda harus gila untuk menulis sesuatu seperti itu, tetapi saya akan senang jika kompiler meneriaki Anda untuk itu. Jadi saya bertanya-tanya, mengapa lvalue initialization dari unique_ptr menjadi sesuatu?

EDIT : Pengguna @aler egal mengutarakan pikiran saya dengan lebih elagif:

"Pada prinsipnya, Anda dapat memiliki konstruktor unique_ptr<int>(int*&& ptr)yang mengasumsikan kepemilikan ptr dan kemudian menyetelnya ke null. Hal itu akan mencegah penggunaan-setelah-bebas dalam contoh khusus ini (karena Anda akan dipaksa std::move(a)dan karena memanggil hapus pada penunjuk null memiliki tidak berpengaruh) tetapi itu akan menjadi anti-pola yang sangat aneh. "

Jawaban

6 d4rk4ng31 Sep 03 2020 at 10:23

Ini adalah contoh klasik tentang apa itu penunjuk yang menggantung ! Berbeda dengan apa yang tampak seperti pointer pintar, mereka hanyalah pembungkus di atas pointer normal (mentah), yang dapat mengelola memori sendiri dan memberi Anda beberapa fungsi tambahan.

Perhatikan contoh berikut:

int* someFunc() {
    int* ptr;
    int* ptr2 = ptr;
    delete ptr;
    return ptr2;
}

Inilah yang pada dasarnya Anda lakukan.
Mereka telah dibuat sedemikian rupa sehingga mereka dapat digunakan sebagai pengganti petunjuk kepemilikan mentah setiap saat ; artinya, mereka juga bisa digantung ! Jadi, jika mereka tidak diizinkan inisialisasi lvalue, itu adalah salah satu kasus penggunaan di mana Anda tidak dapat menggunakan penunjuk cerdas, meskipun saya setuju, ini adalah salah satu kasus di mana Anda juga tidak boleh menggunakannya!

Kode di atas dengan petunjuk cerdas akan persis seperti yang Anda coba.

Sekarang, C ++ meninggalkan logika untuk Anda ... seluruhnya. Jika Anda ingin menembak diri sendiri di kaki, lanjutkan saja, C ++ tidak akan menggonggong. C ++ tidak memeriksa penggunaan memori dan buffer overflows dan akses memori yang dihapus. Sederhananya, ini adalah pekerjaan programmer C ++ dan jika dia ingin menembak dirinya sendiri, dia bebas melakukannya!


Juga, biasanya dianjurkan untuk menggunakan std::make_ptr()ctor, karena yang pertama memungkinkan pengecualian

2 JaMiT Sep 05 2020 at 02:14

Ada prinsip desain terkenal yang menyarankan agar desain tidak hanya sederhana, tetapi juga sangat sederhana. Menambahkan kompleksitas ke implementasi yang sesederhana mungkin membutuhkan justifikasi. Dalam hal ini, pembenaran yang disarankan adalah menghindari hal-hal jahat seperti berikut ini:

std::unique_ptr<int> createInt() {
    int* a = new int(99);
    int* b = a;
    std::unique_ptr<int> foo(b);
    delete a;
    return foo;
}

Ups. Sepertinya saya mengubah sesuatu (memperkenalkan variabel baru, b), tetapi hasil buruk yang sama ada di sana. Hanya ... dalam bentuk ini, memaksa pindah-konstruksi tidak akan membantu. Bahkan jika bdisetel ke nol setelah konstruksi, nilai dari atidak akan tersentuh. (Bentuk ini akan membutuhkan sesuatu yang analog untuk memindahkan-membangun a unique_ptrdari a shared_ptr, tetapi konstruktor seperti itu tidak ada - untuk alasan yang bagus.)

Jadi, Anda akhirnya memperkenalkan komplikasi pada implementasi konstruktor (perlu men-null-out pointer yang disediakan) dan menggunakan konstruktor ( foo(a)harus menjadi foo(std::move(a))), namun masalahnya belum benar-benar terpecahkan. Jika tujuan tertinggi bahasa adalah keamanan, kerumitannya mungkin masih bisa dibenarkan. Namun, C ++ mengurutkan kinerja (Anda hanya membayar untuk apa yang Anda gunakan) di atas keamanan.


Banyak fitur keamanan C ++ yang ada untuk memastikan sesuatu yang baik dilakukan, seperti melepaskan memori. Sangat sedikit yang dilakukan untuk menghentikan sesuatu yang buruk dilakukan, seperti melepaskan memori yang sama dua kali. Beberapa hal buruk memicu peringatan dari beberapa kompiler, namun pada akhirnya programmer mendapatkan apa yang programmer dapatkankeinginan meminta.