Pertanyaannya benar-benar cocok dengan judulnya: Saya penasaran ingin tahu apa alasan teknis dari perbedaan ini, tapi juga alasannya?
std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;
Jawaban:
Itu karena std::shared_ptr
mengimplementasikan penghapusan tipe, sedangkan std::unique_ptr
tidak.
Sejak std::shared_ptr
mengimplementasikan penghapusan tipe, itu juga mendukung properti menarik lainnya , yaitu. itu tidak membutuhkan tipe deleter sebagai argumen tipe template untuk template kelas. Lihat deklarasi mereka:
template<class T,class Deleter = std::default_delete<T> >
class unique_ptr;
yang memiliki Deleter
parameter tipe, sedangkan
template<class T>
class shared_ptr;
tidak memilikinya.
Sekarang pertanyaannya adalah, mengapa shared_ptr
menerapkan penghapusan tipe? Ya, ia melakukannya, karena harus mendukung penghitungan referensi, dan untuk mendukungnya, ia harus mengalokasikan memori dari heap dan karena harus mengalokasikan memori, ia melangkah lebih jauh dan mengimplementasikan penghapusan tipe - yang membutuhkan tumpukan alokasi juga. Jadi pada dasarnya itu hanya menjadi oportunis!
Karena tipe-erasure, std::shared_ptr
mampu mendukung dua hal:
void*
, namun ia masih dapat menghapus objek yang sedang dihancurkan dengan benar dengan menjalankan destruktornya dengan benar .Baik. Itu semua tentang cara std::shared_ptr
kerjanya.
Sekarang pertanyaannya adalah, dapatkah std::unique_ptr
menyimpan objek sebagai void*
? Jawabannya adalah, ya - asalkan Anda memberikan deleter yang sesuai sebagai argumen. Inilah salah satu demonstrasi tersebut:
int main()
{
auto deleter = [](void const * data ) {
int const * p = static_cast<int const*>(data);
std::cout << *p << " located at " << p << " is being deleted";
delete p;
};
std::unique_ptr<void, decltype(deleter)> p(new int(959), deleter);
} //p will be deleted here, both p ;-)
Output ( demo online ):
959 located at 0x18aec20 is being deleted
Anda mengajukan pertanyaan yang sangat menarik di komentar:
Dalam kasus saya, saya akan memerlukan penghapus penghapus tipe, tetapi tampaknya mungkin juga (dengan biaya alokasi tumpukan). Pada dasarnya, apakah ini berarti sebenarnya ada tempat khusus untuk tipe ketiga penunjuk cerdas: penunjuk cerdas kepemilikan eksklusif dengan penghapusan jenis.
yang @Steve Jessop menyarankan solusi berikut,
Saya belum pernah benar-benar mencoba ini, tetapi mungkin Anda dapat mencapai itu dengan menggunakan yang sesuai
std::function
sebagai tipe deleter denganunique_ptr
? Misalkan itu benar-benar berfungsi maka Anda sudah selesai, kepemilikan eksklusif dan penghapus tipe-terhapus.
Mengikuti saran ini, saya menerapkan ini (meskipun tidak memanfaatkan std::function
karena tampaknya tidak perlu):
using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>;
template<typename T>
auto unique_void(T * ptr) -> unique_void_ptr
{
return unique_void_ptr(ptr, [](void const * data) {
T const * p = static_cast<T const*>(data);
std::cout << "{" << *p << "} located at [" << p << "] is being deleted.\n";
delete p;
});
}
int main()
{
auto p1 = unique_void(new int(959));
auto p2 = unique_void(new double(595.5));
auto p3 = unique_void(new std::string("Hello World"));
}
Output ( demo online ):
{Hello World} located at [0x2364c60] is being deleted.
{595.5} located at [0x2364c40] is being deleted.
{959} located at [0x2364c20] is being deleted.
Semoga membantu.
std::function
sebagai tipe deleter dengan unique_ptr
? Misalkan itu benar-benar berfungsi maka Anda sudah selesai, kepemilikan eksklusif dan penghapus tipe-terhapus.
Salah satu alasannya adalah di salah satu dari banyak kasus penggunaan a shared_ptr
- yaitu sebagai indikator seumur hidup atau sentinel.
Ini disebutkan dalam dokumentasi pendorong asli:
auto register_callback(std::function<void()> closure, std::shared_ptr<void> pv)
{
auto closure_target = { closure, std::weak_ptr<void>(pv) };
...
// store the target somewhere, and later....
}
void call_closure(closure_target target)
{
// test whether target of the closure still exists
auto lock = target.sentinel.lock();
if (lock) {
// if so, call the closure
target.closure();
}
}
Dimana closure_target
sesuatu seperti ini:
struct closure_target {
std::function<void()> closure;
std::weak_ptr<void> sentinel;
};
Penelepon akan mendaftarkan panggilan balik seperti ini:
struct active_object : std::enable_shared_from_this<active_object>
{
void start() {
event_emitter_.register_callback([this] { this->on_callback(); },
shared_from_this());
}
void on_callback()
{
// this is only ever called if we still exist
}
};
karena shared_ptr<X>
selalu dapat diubah shared_ptr<void>
, event_emitter sekarang dapat dengan senang hati tidak menyadari jenis objek yang dipanggil kembali.
Pengaturan ini melepaskan pelanggan ke event emitter dari kewajiban menangani kasus penyeberangan (bagaimana jika callback masuk dalam antrian, menunggu untuk ditindaklanjuti sementara active_object menghilang?), Dan juga berarti tidak perlu menyinkronkan berhenti berlangganan. weak_ptr<void>::lock
adalah operasi tersinkronisasi.
std::unique_ptr<void, D>
masih mungkin dengan menyediakan yang sesuaiD
.