Saya memiliki beberapa kode di header yang terlihat seperti ini:
#include <memory>
class Thing;
class MyClass
{
std::unique_ptr< Thing > my_thing;
};
Jika saya menyertakan header ini dalam cpp yang tidak menyertakan Thing
definisi tipe, maka ini tidak dikompilasi di bawah VS2010-SP1:
1> C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ memory (2067): error C2027: penggunaan tipe 'Hal' yang tidak ditentukan
Ganti std::unique_ptr
oleh std::shared_ptr
dan dikompilasi.
Jadi, saya menduga bahwa itu adalah std::unique_ptr
implementasi VS2010 saat ini yang membutuhkan definisi penuh dan itu sepenuhnya tergantung pada implementasi.
Atau itu? Apakah ada sesuatu di dalamnya persyaratan standar yang membuat std::unique_ptr
implementasi tidak mungkin bekerja hanya dengan deklarasi maju? Rasanya aneh karena seharusnya hanya memegang pointer Thing
, bukan?
shared_ptr
/unique_ptr
" Tabel di bagian akhir harus menjawab pertanyaan Anda.Jawaban:
Diadopsi dari sini .
Sebagian besar templat di pustaka standar C ++ mengharuskan mereka dipakai dengan tipe lengkap. Namun
shared_ptr
danunique_ptr
merupakan pengecualian parsial . Beberapa, tetapi tidak semua anggota mereka dapat dipakai dengan tipe tidak lengkap. Motivasi untuk ini adalah untuk mendukung idiom seperti jerawat menggunakan pointer pintar, dan tanpa risiko perilaku yang tidak terdefinisi.Perilaku tidak terdefinisi dapat terjadi ketika Anda memiliki tipe yang tidak lengkap dan Anda memanggilnya
delete
:Kode di atas adalah hukum. Itu akan dikompilasi. Kompiler Anda mungkin atau mungkin tidak memancarkan peringatan untuk kode di atas seperti di atas. Ketika dijalankan, hal-hal buruk mungkin akan terjadi. Jika Anda sangat beruntung program Anda akan macet. Namun hasil yang lebih mungkin adalah bahwa program Anda akan secara diam-diam membocorkan memori karena
~A()
tidak akan dipanggil.Menggunakan
auto_ptr<A>
contoh di atas tidak membantu. Anda masih mendapatkan perilaku tidak terdefinisi yang sama seolah-olah Anda menggunakan pointer mentah.Meskipun demikian, menggunakan kelas yang tidak lengkap di tempat-tempat tertentu sangat berguna! Di sinilah
shared_ptr
danunique_ptr
membantu. Penggunaan salah satu dari pointer cerdas ini akan memungkinkan Anda untuk pergi dengan tipe yang tidak lengkap, kecuali jika perlu untuk memiliki tipe yang lengkap. Dan yang paling penting, ketika perlu memiliki tipe yang lengkap, Anda mendapatkan kesalahan waktu kompilasi jika Anda mencoba menggunakan smart pointer dengan tipe yang tidak lengkap pada saat itu.Tidak ada lagi perilaku yang tidak terdefinisi:
Jika kode Anda dikompilasi, maka Anda telah menggunakan tipe lengkap di mana pun Anda perlu.
shared_ptr
danunique_ptr
membutuhkan jenis yang lengkap di tempat yang berbeda. Alasannya tidak jelas, berkaitan dengan deleter dinamis vs deleter statis. Alasan tepatnya tidak penting. Faktanya, dalam kebanyakan kode tidak terlalu penting bagi Anda untuk mengetahui dengan tepat di mana jenis yang lengkap diperlukan. Hanya kode, dan jika Anda salah, kompiler akan memberi tahu Anda.Namun, jika berguna bagi Anda, berikut adalah tabel yang mendokumentasikan beberapa anggota
shared_ptr
danunique_ptr
berkenaan dengan persyaratan kelengkapan. Jika anggota membutuhkan tipe yang lengkap, maka entri memiliki "C", jika tidak, entri tabel diisi dengan "I".Operasi apa pun yang memerlukan konversi pointer memerlukan tipe yang lengkap untuk keduanya
unique_ptr
danshared_ptr
.The
unique_ptr<A>{A*}
konstruktor bisa lolos dengan lengkapA
hanya jika compiler tidak diperlukan untuk membuat sebuah panggilan untuk~unique_ptr<A>()
. Misalnya jika Anda meletakkannyaunique_ptr
di tumpukan, Anda bisa lolos dengan tidak lengkapA
. Rincian lebih lanjut tentang hal ini dapat ditemukan dalam BarryTheHatchet ini jawaban di sini .sumber
unique_ptr
sebagai variabel anggota kelas, hanya secara eksplisit menyatakan destructor (dan konstruktor) dalam deklarasi kelas (dalam file header) dan melanjutkan ke mendefinisikan mereka dalam file sumber (dan letakkan header dengan deklarasi lengkap kelas runcing ke dalam file sumber) untuk mencegah kompilator secara otomatis memasukkan konstruktor atau destruktor pada file header (yang memicu kesalahan). stackoverflow.com/a/13414884/368896 juga membantu mengingatkan saya akan hal ini.Kompiler memerlukan definisi Hal untuk menghasilkan destruktor default untuk MyClass. Jika Anda secara eksplisit mendeklarasikan destructor dan memindahkan implementasinya (kosong) ke file CPP, kode tersebut harus dikompilasi.
sumber
MyClass::~MyClass() = default;
dalam file implementasi tampaknya kurang mungkin dihapus secara tidak sengaja nanti di jalan oleh seseorang yang menganggap tubuh destuctor telah dihapus daripada sengaja dibiarkan kosong.default
ed dandelete
d.MyClass::~MyClass() = default
tidak memindahkannya ke file implementasi di Dentang. (belum?)Ini tidak tergantung pada implementasi. Alasan kerjanya adalah karena
shared_ptr
menentukan destructor yang tepat untuk dipanggil saat run-time - itu bukan bagian dari tipe signature. Namun,unique_ptr
destructor adalah bagian dari tipenya, dan harus diketahui pada saat kompilasi.sumber
Sepertinya jawaban saat ini tidak benar-benar memaku mengapa konstruktor default (atau destruktor) adalah masalah tetapi yang kosong dinyatakan dalam cpp tidak.
Inilah yang terjadi:
Jika kelas luar (yaitu MyClass) tidak memiliki konstruktor atau destruktor maka kompiler menghasilkan yang standar. Masalah dengan ini adalah bahwa kompiler pada dasarnya menyisipkan konstruktor / destruktor kosong default dalam file .hpp. Ini berarti bahwa kode untuk contructor / destructor default akan dikompilasi bersama dengan biner yang dapat dieksekusi host, tidak bersama dengan biner perpustakaan Anda. Namun definisi ini tidak dapat benar-benar membangun kelas parsial. Jadi ketika linker masuk ke biner perpustakaan Anda dan mencoba untuk mendapatkan konstruktor / destruktor, itu tidak menemukan apapun dan Anda mendapatkan kesalahan. Jika kode konstruktor / destruktor ada di .cpp Anda maka biner perpustakaan Anda memiliki yang tersedia untuk ditautkan.
Ini tidak ada hubungannya dengan menggunakan unique_ptr atau shared_ptr dan jawaban lain tampaknya mungkin membingungkan bug di VC ++ lama untuk implementasi unique_ptr (VC ++ 2015 berfungsi dengan baik di mesin saya).
Jadi moral dari cerita ini adalah bahwa tajuk Anda harus tetap bebas dari definisi konstruktor / destruktor. Itu hanya dapat berisi deklarasi mereka. Misalnya,
~MyClass()=default;
dalam hpp tidak akan berfungsi. Jika Anda mengizinkan kompiler untuk memasukkan konstruktor atau destruktor default, Anda akan mendapatkan kesalahan linker.Catatan sisi lain: Jika Anda masih mendapatkan kesalahan ini bahkan setelah Anda memiliki konstruktor dan destruktor dalam file cpp maka kemungkinan besar alasannya adalah bahwa perpustakaan Anda tidak dapat dikompilasi dengan benar. Sebagai contoh, suatu kali saya hanya mengubah jenis proyek dari Console ke Library di VC ++ dan saya mendapatkan kesalahan ini karena VC ++ tidak menambahkan simbol preprocessor _LIB dan yang menghasilkan pesan kesalahan yang sama persis.
sumber
Hanya untuk kelengkapan:
Header: Ah
Sumber A.cpp:
Definisi kelas B harus dilihat oleh konstruktor, destruktor, dan apa pun yang mungkin secara implisit menghapus B. (Meskipun konstruktor tidak muncul dalam daftar di atas, dalam VS2017 bahkan konstruktor memerlukan definisi B. Dan ini masuk akal ketika mempertimbangkan bahwa dalam kasus pengecualian pada constructor, unique_ptr dihancurkan lagi.)
sumber
Definisi penuh dari Hal ini diperlukan pada titik instantiasi template. Ini adalah alasan yang tepat mengapa idiom pompl mengkompilasi.
Jika tidak mungkin, orang tidak akan bertanya seperti ini .
sumber
Jawaban sederhananya adalah gunakan shared_ptr saja.
sumber
Bagi saya,
Cukup sertakan tajuk ...
sumber