Saya memiliki beberapa kode templat yang saya ingin simpan dalam file CPP daripada inline di header. Saya tahu ini bisa dilakukan selama Anda tahu jenis templat mana yang akan digunakan. Sebagai contoh:
file .h
class foo
{
public:
template <typename T>
void do(const T& t);
};
file .cpp
template <typename T>
void foo::do(const T& t)
{
// Do something with t
}
template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);
Perhatikan dua baris terakhir - fungsi templat foo :: do hanya digunakan dengan ints dan std :: strings, jadi definisi tersebut berarti aplikasi akan ditautkan.
Pertanyaan saya adalah - apakah ini hack jahat atau akankah ini bekerja dengan kompiler / penghubung lainnya? Saya hanya menggunakan kode ini dengan VS2008 saat ini tetapi akan ingin port ke lingkungan lain.
do
sebagai pengidentifikasi: ptemplate class foo<int>;template class foo<std::string>;
di akhir file .cpp?Jawaban:
Masalah yang Anda uraikan dapat diselesaikan dengan menentukan templat di header, atau melalui pendekatan yang Anda jelaskan di atas.
Saya merekomendasikan membaca poin-poin berikut dari C ++ FAQ Lite :
Mereka masuk ke banyak detail tentang ini (dan lainnya) masalah template.
sumber
Bagi yang lain di halaman ini bertanya-tanya apa sintaks yang benar (seperti yang saya lakukan) untuk spesialisasi template eksplisit (atau setidaknya dalam VS2008), berikut ini ...
Dalam file .h Anda ...
Dan dalam file .cpp Anda
sumber
Kode ini terbentuk dengan baik. Anda hanya perlu memperhatikan bahwa definisi template terlihat pada titik instantiation. Mengutip standar, § 14.7.2.4:
sumber
a.cpp
(mendefinisikan fungsia() {}
) danb.cpp
(mendefinisikan fungsib() { a() }
), maka ini akan berhasil ditautkan. Jika saya benar, maka kutipan di atas sepertinya tidak berlaku untuk kasus umum ... apakah saya salah di suatu tempat?inline
fungsiinline
. Alasannya adalah bahwa tanpa ABI standar C ++ sulit / tidak mungkin untuk mendefinisikan efek yang seharusnya dimiliki.Ini akan berfungsi dengan baik di mana pun templat didukung. Instansiasi template eksplisit adalah bagian dari standar C ++.
sumber
Contoh Anda benar tetapi tidak terlalu portabel. Ada juga sintaks yang sedikit lebih bersih yang dapat digunakan (seperti yang ditunjukkan oleh @ namespace-sid).
Misalkan kelas templated adalah bagian dari beberapa perpustakaan yang akan dibagikan. Haruskah versi lain dari kelas templated dikompilasi? Apakah pengelola perpustakaan seharusnya mengantisipasi semua kemungkinan penggunaan kelas yang templated?
Pendekatan alternatif adalah sedikit variasi pada apa yang Anda miliki: tambahkan file ketiga yang merupakan file templat implementasi / instantiation.
file foo.h
file foo.cpp
file foo-impl.cpp
Satu peringatan adalah bahwa Anda perlu memberitahu kompiler untuk mengkompilasi,
foo-impl.cpp
bukanfoo.cpp
sebagai kompilasi yang terakhir tidak melakukan apa-apa.Tentu saja, Anda dapat memiliki beberapa implementasi di file ketiga atau memiliki beberapa file implementasi untuk setiap jenis yang ingin Anda gunakan.
Ini memungkinkan lebih banyak fleksibilitas saat berbagi kelas templated untuk penggunaan lain.
Setup ini juga mengurangi waktu kompilasi untuk kelas yang digunakan kembali karena Anda tidak mengkompilasi ulang file header yang sama di setiap unit terjemahan.
sumber
foo.cpp
) dari mana versi sebenarnya dikompilasi (dalamfoo-impl.cpp
) dan deklarasi (dalamfoo.h
). Saya tidak suka bahwa sebagian besar template C ++ didefinisikan seluruhnya dalam file header. Itu berlawanan dengan standar C / C ++ pasanganc[pp]/h
untuk setiap kelas / namespace / pengelompokan apa pun yang Anda gunakan. Orang-orang tampaknya masih menggunakan file header monolitik hanya karena alternatif ini tidak banyak digunakan atau diketahui.h/cpp
pasangan meskipun saya harus mengelilingi daftar instantiations asli dalam penjaga termasuk, tetapi saya masih bisa mengkompilasifoo.cpp
seperti biasa. Saya masih cukup baru untuk C ++ dan akan tertarik untuk mengetahui apakah penggunaan campuran ini memiliki peringatan tambahan.foo.cpp
danfoo-impl.cpp
. Jangan#include "foo.cpp"
difoo-impl.cpp
file; sebaliknya, tambahkan deklarasiextern template class foo<int>;
untukfoo.cpp
mencegah compiler dari instantiating template saat kompilasifoo.cpp
. Pastikan bahwa sistem build membangun kedua.cpp
file dan meneruskan kedua file objek ke linker. Ini memiliki banyak manfaat: a) jelasfoo.cpp
bahwa tidak ada instantiasi; b) perubahan ke foo.cpp tidak memerlukan kompilasi ulang foo-impl.cpp.foo.cpp
menjadifoo_impl.h
danfoo-impl.cpp
menjadi adilfoo.cpp
. Saya juga akan menambahkan typedefs untuk instantiations darifoo.cpp
kefoo.h
, jugausing foo_int = foo<int>;
. Caranya adalah memberi pengguna dua antarmuka tajuk untuk suatu pilihan. Ketika pengguna membutuhkan instantiasi yang telah ditentukan sebelumnya ia sertakanfoo.h
, ketika pengguna membutuhkan sesuatu yang tidak berurutan yang ia sertakanfoo_impl.h
.Ini jelas bukan peretasan yang tidak menyenangkan, tetapi perhatikan fakta bahwa Anda harus melakukannya (spesialisasi templat eksplisit) untuk setiap kelas / jenis yang ingin Anda gunakan dengan templat yang diberikan. Dalam hal BANYAK jenis meminta instantiasi template mungkin ada BANYAK baris dalam file .cpp Anda. Untuk memperbaiki masalah ini, Anda dapat memiliki TemplateClassInst.cpp di setiap proyek yang Anda gunakan sehingga Anda memiliki kontrol yang lebih besar jenis apa yang akan dipakai. Jelas solusi ini tidak akan sempurna (alias peluru perak) karena Anda mungkin akhirnya melanggar ODR :).
sumber
Ada, dalam standar terbaru, kata kunci (
export
) yang akan membantu meringankan masalah ini, tetapi tidak diterapkan dalam kompiler yang saya ketahui, selain Comeau.Lihat FAQ-lite tentang ini.
sumber
Itu adalah cara standar untuk mendefinisikan fungsi template. Saya pikir ada tiga metode yang saya baca untuk mendefinisikan template. Atau mungkin 4. Masing-masing dengan pro dan kontra.
Definisikan dalam definisi kelas. Saya tidak suka ini sama sekali karena saya pikir definisi kelas hanya untuk referensi dan harus mudah dibaca. Namun jauh lebih sulit untuk mendefinisikan template di kelas daripada di luar. Dan tidak semua deklarasi templat memiliki tingkat kerumitan yang sama. Metode ini juga menjadikan templat sebagai templat yang benar.
Tetapkan template di header yang sama, tetapi di luar kelas. Ini adalah cara yang paling saya sukai sebagian besar waktu. Itu membuat definisi kelas Anda rapi, template tetap menjadi template yang sebenarnya. Namun itu membutuhkan penamaan template lengkap yang bisa rumit. Juga, kode Anda tersedia untuk semua. Tetapi jika Anda membutuhkan kode Anda untuk sebaris ini adalah satu-satunya cara. Anda juga dapat melakukannya dengan membuat file .INL di akhir definisi kelas Anda.
Sertakan header.h dan implementasi.CPP ke main.CPP Anda. Saya pikir itulah caranya. Anda tidak perlu menyiapkan pre instantiations apa pun, itu akan berperilaku seperti template yang benar. Masalah yang saya miliki dengan itu adalah bahwa itu tidak wajar. Kami biasanya tidak memasukkan dan berharap untuk memasukkan file sumber. Saya kira karena Anda memasukkan file sumber, fungsi template dapat diuraikan.
Metode terakhir ini, yang merupakan cara diposting, mendefinisikan template dalam file sumber, seperti nomor 3; tetapi alih-alih memasukkan file sumber, kami membuat instantiate templat ke templat yang akan kami butuhkan. Saya tidak memiliki masalah dengan metode ini dan kadang-kadang berguna. Kami memiliki satu kode besar, tidak dapat manfaat dari inline jadi masukkan saja ke dalam file CPP. Dan jika kita tahu contoh umum dan kita bisa menentukannya. Ini menyelamatkan kita dari menulis pada dasarnya hal yang sama 5, 10 kali. Metode ini memiliki keuntungan menjaga kode tetap milik kami. Tapi saya tidak merekomendasikan menempatkan fungsi kecil, yang biasa digunakan dalam file CPP. Karena ini akan mengurangi kinerja perpustakaan Anda.
Catatan, saya tidak mengetahui konsekuensi dari file obj yang membengkak.
sumber
Ya, itulah cara standar untuk melakukan instantiasi eksplisit
khusus. Seperti yang Anda nyatakan, Anda tidak dapat membuat contoh template ini dengan tipe lain.Edit: dikoreksi berdasarkan komentar.
sumber
Mari kita ambil satu contoh, katakanlah untuk beberapa alasan Anda ingin memiliki kelas templat:
Jika Anda mengkompilasi kode ini dengan Visual Studio - itu bekerja di luar kotak. gcc akan menghasilkan kesalahan tautan (jika file header yang sama digunakan dari banyak file .cpp):
Dimungkinkan untuk memindahkan implementasi ke file .cpp, tetapi kemudian Anda harus mendeklarasikan kelas seperti ini -
Dan kemudian .cpp akan terlihat seperti ini:
Tanpa dua baris terakhir dalam file header - gcc akan berfungsi dengan baik, tetapi Visual studio akan menghasilkan kesalahan:
sintaks kelas template adalah opsional jika Anda ingin mengekspos fungsi melalui ekspor .dll, tetapi ini hanya berlaku untuk platform windows - jadi test_template.h bisa terlihat seperti ini:
dengan file .cpp dari contoh sebelumnya.
Namun ini memberikan lebih banyak sakit kepala ke tautan, jadi disarankan untuk menggunakan contoh sebelumnya jika Anda tidak mengekspor fungsi .dll.
sumber
Waktunya untuk pembaruan! Buat file inline (.inl, atau mungkin yang lain) dan cukup salin semua definisi Anda di dalamnya. Pastikan untuk menambahkan templat di atas setiap fungsi (
template <typename T, ...>
). Sekarang alih-alih memasukkan file header di file inline Anda melakukan sebaliknya. Sertakan file inline setelah deklarasi kelas Anda (#include "file.inl"
).Saya tidak benar-benar tahu mengapa tidak ada yang menyebutkan ini. Saya tidak melihat kelemahan langsung.
sumber
#include "file.inl"
, preprocessor akan menempelkan kontenfile.inl
langsung ke header. Apa pun alasan Anda ingin menghindari implementasi di header, solusi ini tidak menyelesaikan masalah itu.template
definisi - definisi yang tidak sesuai . Saya mengerti mengapa orang ingin melakukannya - untuk mencapai paritas paling banyak dengan deklarasi / definisi non-template, untuk menjaga deklarasi antarmuka terlihat rapi, dll. - tetapi itu tidak selalu sepadan dengan kerumitannya. Ini adalah kasus mengevaluasi trade-off di kedua sisi dan memilih yang paling buruk . ... sampainamespace class
menjadi suatu hal: O [ harap menjadi sesuatu ]Tidak ada yang salah dengan contoh yang Anda berikan. Tetapi saya harus mengatakan saya percaya itu tidak efisien untuk menyimpan definisi fungsi dalam file cpp. Saya hanya mengerti kebutuhan untuk memisahkan deklarasi dan definisi fungsi.
Ketika digunakan bersama dengan instantiation kelas eksplisit, Boost Concept Check Library (BCCL) dapat membantu Anda menghasilkan kode fungsi template dalam file cpp.
sumber
Tidak ada yang di atas bekerja untuk saya, jadi di sini adalah bagaimana Anda menyelesaikannya, kelas saya hanya memiliki 1 metode templated ..
.h
.cpp
ini menghindari kesalahan penghubung, dan tidak perlu memanggil Fungsi Sementara sama sekali
sumber