Saya menggunakan idiom-jerawat dengan std::unique_ptr
:
class window {
window(const rectangle& rect);
private:
class window_impl; // defined elsewhere
std::unique_ptr<window_impl> impl_; // won't compile
};
Namun, saya mendapatkan kesalahan kompilasi tentang penggunaan tipe yang tidak lengkap, pada baris 304 di <memory>
:
Aplikasi '
sizeof
' ke jenis yang tidak lengkap 'uixx::window::window_impl
' tidak valid
Sejauh yang saya tahu, std::unique_ptr
harus bisa digunakan dengan tipe yang tidak lengkap. Apakah ini bug di libc ++ atau saya melakukan sesuatu yang salah di sini?
Jawaban:
Berikut adalah beberapa contoh
std::unique_ptr
dengan tipe tidak lengkap. Masalahnya terletak pada kehancuran.Jika Anda menggunakan pimpl
unique_ptr
, Anda harus mendeklarasikan sebuah destruktor:karena jika tidak kompiler menghasilkan yang default, dan perlu deklarasi lengkap
foo::impl
untuk ini.Jika Anda memiliki konstruktor template, maka Anda kacau, bahkan jika Anda tidak membangun
impl_
anggota:Pada lingkup namespace, menggunakan
unique_ptr
tidak akan berfungsi:karena kompiler harus tahu di sini cara menghancurkan objek durasi statis ini. Solusi adalah:
sumber
foo::~foo() = default;
dalam file srcSeperti yang disebutkan oleh Alexandre C. , masalahnya
window
adalah destruktor didefinisikan secara implisit di tempat-tempat di mana tipewindow_impl
masih belum lengkap. Selain solusinya, solusi lain yang saya gunakan adalah mendeklarasikan fungsi Deleter di header:Perhatikan bahwa menggunakan fungsi Deleter kustom menghalangi penggunaan
std::make_unique
(tersedia dari C ++ 14), seperti yang sudah dibahas di sini .sumber
gunakan deleter khusus
Masalahnya adalah bahwa
unique_ptr<T>
harus memanggil destructorT::~T()
di destructor sendiri, operator penugasan langkahnya, danunique_ptr::reset()
fungsi anggota (hanya). Namun, ini harus dipanggil (secara implisit atau eksplisit) dalam beberapa situasi PIMPL (sudah ada di destruktor kelas luar dan operator penugasan pemindahan).Seperti sudah ditunjukkan dalam jawaban lain, salah satu cara untuk menghindari itu adalah untuk memindahkan semua operasi yang membutuhkan
unique_ptr::~unique_ptr()
,unique_ptr::operator=(unique_ptr&&)
danunique_ptr::reset()
ke dalam file sumber di mana kelas pimpl helper sebenarnya didefinisikan.Namun, ini agak merepotkan dan menentang titik pimpl idoim sampai batas tertentu. Solusi yang jauh lebih bersih yang menghindari semua itu adalah dengan menggunakan custom deleter dan hanya memindahkan definisinya ke file sumber tempat tinggal kelas pembantu jerawat. Ini adalah contoh sederhana:
Alih-alih kelas deleter yang terpisah, Anda juga dapat menggunakan fungsi gratis atau
static
anggotafoo
dalam hubungannya dengan lambda:sumber
Mungkin Anda memiliki beberapa fungsi tubuh dalam file .h dalam kelas yang menggunakan tipe tidak lengkap.
Pastikan bahwa dalam .h Anda untuk jendela kelas Anda hanya memiliki deklarasi fungsi. Semua badan fungsi untuk jendela harus dalam file .cpp. Dan untuk window_impl juga ...
Btw, Anda harus secara eksplisit menambahkan deklarasi destructor untuk kelas windows di file .h Anda.
Tapi Anda TIDAK BISA memasukkan tubuh dokumen kosong ke file header Anda:
Harus hanya deklarasi:
sumber
Untuk menambah balasan orang lain tentang deleter khusus, di "perpustakaan utilitas" internal kami, saya menambahkan header pembantu untuk menerapkan pola umum ini (
std::unique_ptr
dari tipe yang tidak lengkap, hanya diketahui oleh beberapa TU untuk mis. Menghindari waktu kompilasi yang lama atau untuk menyediakan hanya pegangan buram untuk klien).Ini menyediakan perancah umum untuk pola ini: kelas deleter kustom yang memanggil fungsi deleter yang ditentukan secara eksternal, tipe alias untuk dengan
unique_ptr
dengan kelas deleter ini, dan makro untuk mendeklarasikan fungsi deleter dalam TU yang memiliki definisi lengkap dari Tipe. Saya pikir ini memiliki kegunaan umum, jadi ini dia:sumber
Mungkin bukan solusi terbaik, tetapi terkadang Anda dapat menggunakan shared_ptr sebagai gantinya. Jika tentu saja ini agak berlebihan, tapi ... seperti untuk unique_ptr, saya mungkin akan menunggu 10 tahun lagi sampai pembuat standar C ++ akan memutuskan untuk menggunakan lambda sebagai deleter.
Sisi lain. Per kode Anda itu mungkin terjadi, bahwa pada tahap kehancuran window_impl tidak akan lengkap. Ini bisa menjadi alasan perilaku yang tidak terdefinisi. Lihat ini: Mengapa, sungguh, menghapus tipe yang tidak lengkap adalah perilaku yang tidak terdefinisi?
Jadi, jika mungkin saya akan mendefinisikan objek yang sangat dasar untuk semua objek Anda, dengan destruktor virtual. Dan kamu hampir baik. Anda hanya perlu mengingat bahwa sistem akan memanggil penghancur virtual untuk pointer Anda, jadi Anda harus mendefinisikannya untuk setiap leluhur. Anda juga harus mendefinisikan kelas dasar di bagian warisan sebagai virtual (lihat ini untuk detailnya).
sumber