Setelah istirahat beberapa minggu, saya mencoba untuk memperluas dan memperluas pengetahuan saya tentang templat dengan buku Templat - Panduan Lengkap oleh David Vandevoorde dan Nicolai M. Josuttis, dan apa yang saya coba pahami saat ini adalah contoh eksplisit templat .
Saya sebenarnya tidak memiliki masalah dengan mekanismenya, tetapi saya tidak dapat membayangkan situasi di mana saya ingin atau ingin menggunakan fitur ini. Jika ada yang bisa menjelaskan itu kepada saya, saya akan sangat berterima kasih.
Jika Anda mendefinisikan kelas template yang hanya ingin Anda kerjakan untuk beberapa tipe eksplisit.
Letakkan deklarasi template di file header seperti kelas normal.
Letakkan definisi template dalam file sumber seperti kelas normal.
Kemudian, di akhir file sumber, secara eksplisit hanya membuat contoh versi yang Anda inginkan untuk tersedia.
Contoh konyol:
// StringAdapter.h template<typename T> class StringAdapter { public: StringAdapter(T* data); void doAdapterStuff(); private: std::basic_string<T> m_data; }; typedef StringAdapter<char> StrAdapter; typedef StringAdapter<wchar_t> WStrAdapter;
Sumber:
// StringAdapter.cpp #include "StringAdapter.h" template<typename T> StringAdapter<T>::StringAdapter(T* data) :m_data(data) {} template<typename T> void StringAdapter<T>::doAdapterStuff() { /* Manipulate a string */ } // Explicitly instantiate only the classes you want to be defined. // In this case I only want the template to work with characters but // I want to support both char and wchar_t with the same code. template class StringAdapter<char>; template class StringAdapter<wchar_t>;
Utama
#include "StringAdapter.h" // Note: Main can not see the definition of the template from here (just the declaration) // So it relies on the explicit instantiation to make sure it links. int main() { StrAdapter x("hi There"); x.doAdapterStuff(); }
sumber
Instansiasi eksplisit memungkinkan untuk mengurangi waktu kompilasi dan ukuran objek
Ini adalah keuntungan utama yang dapat diberikannya. Mereka berasal dari dua efek berikut yang dijelaskan secara rinci pada bagian di bawah ini:
Hapus definisi dari header
Instansiasi eksplisit memungkinkan Anda meninggalkan definisi di file .cpp.
Jika definisinya ada di header dan Anda memodifikasinya, sistem build yang cerdas akan mengkompilasi ulang semua termasuk, yang bisa berupa lusinan file, membuat kompilasi menjadi sangat lambat.
Menempatkan definisi dalam file .cpp memiliki sisi negatif bahwa library eksternal tidak dapat menggunakan kembali template dengan kelas barunya sendiri, tetapi "Hapus definisi dari header yang disertakan, tetapi juga buka template API eksternal" di bawah ini menunjukkan solusi.
Lihat contoh konkret di bawah ini.
Keuntungan redefinisi objek: memahami masalah
Jika Anda baru saja menentukan template sepenuhnya pada file header, setiap unit kompilasi yang menyertakan header tersebut akhirnya mengompilasi salinan implisit template untuk setiap penggunaan argumen template berbeda yang dibuat.
Ini berarti banyak penggunaan disk dan waktu kompilasi yang tidak berguna.
Berikut ini adalah contoh konkret, di mana kedua
main.cpp
dannotmain.cpp
secara implisit menentukanMyTemplate<int>
karena penggunaannya dalam file-file.main.cpp
#include <iostream> #include "mytemplate.hpp" #include "notmain.hpp" int main() { std::cout << notmain() + MyTemplate<int>().f(1) << std::endl; }
notmain.cpp
#include "mytemplate.hpp" #include "notmain.hpp" int notmain() { return MyTemplate<int>().f(1); }
mytemplate.hpp
#ifndef MYTEMPLATE_HPP #define MYTEMPLATE_HPP template<class T> struct MyTemplate { T f(T t) { return t + 1; } }; #endif
notmain.hpp
#ifndef NOTMAIN_HPP #define NOTMAIN_HPP int notmain(); #endif
GitHub upstream .
Kompilasi dan lihat simbol dengan
nm
:g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o notmain.o notmain.cpp g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o main.o main.cpp g++ -Wall -Wextra -std=c++11 -pedantic-errors -o main.out notmain.o main.o echo notmain.o nm -C -S notmain.o | grep MyTemplate echo main.o nm -C -S main.o | grep MyTemplate
Keluaran:
notmain.o 0000000000000000 0000000000000017 W MyTemplate<int>::f(int) main.o 0000000000000000 0000000000000017 W MyTemplate<int>::f(int)
Dari
man nm
, kita melihat ituW
berarti simbol lemah, yang dipilih GCC karena ini adalah fungsi template. Simbol yang lemah berarti bahwa kode yang dibuat secara implisit untukMyTemplate<int>
dikompilasi pada kedua file.Alasan mengapa itu tidak meledak pada waktu tautan dengan beberapa definisi adalah bahwa penaut menerima beberapa definisi yang lemah, dan hanya memilih salah satunya untuk dimasukkan ke dalam eksekusi akhir.
Angka-angka dalam output berarti:
0000000000000000
: alamat dalam bagian. Nol ini karena templat secara otomatis dimasukkan ke bagiannya sendiri0000000000000017
: ukuran kode yang dibuat untuk merekaKita dapat melihat ini sedikit lebih jelas dengan:
yang diakhiri dengan:
Disassembly of section .text._ZN10MyTemplateIiE1fEi: 0000000000000000 <MyTemplate<int>::f(int)>: 0: f3 0f 1e fa endbr64 4: 55 push %rbp 5: 48 89 e5 mov %rsp,%rbp 8: 48 89 7d f8 mov %rdi,-0x8(%rbp) c: 89 75 f4 mov %esi,-0xc(%rbp) f: 8b 45 f4 mov -0xc(%rbp),%eax 12: 83 c0 01 add $0x1,%eax 15: 5d pop %rbp 16: c3 retq
dan
_ZN10MyTemplateIiE1fEi
merupakan nama yang rusakMyTemplate<int>::f(int)>
yangc++filt
memutuskan untuk tidak dibongkar.Jadi kita melihat bahwa bagian terpisah dibuat untuk setiap instansiasi metode, dan masing-masing dari mereka tentu saja membutuhkan ruang dalam file objek.
Solusi untuk masalah definisi ulang objek
Masalah ini dapat dihindari dengan menggunakan instansiasi eksplisit dan:
pertahankan definisi di hpp dan tambahkan
extern template
hpp untuk tipe-tipe yang akan dipakai secara eksplisit.Seperti yang dijelaskan di: using extern template (C ++ 11)
extern template
mencegah template yang sepenuhnya ditentukan dibuat instance-nya oleh unit kompilasi, kecuali untuk instance eksplisit kami. Dengan cara ini, hanya instansiasi eksplisit kita yang akan didefinisikan di objek akhir:mytemplate.hpp
#ifndef MYTEMPLATE_HPP #define MYTEMPLATE_HPP template<class T> struct MyTemplate { T f(T t) { return t + 1; } }; extern template class MyTemplate<int>; #endif
mytemplate.cpp
#include "mytemplate.hpp" // Explicit instantiation required just for int. template class MyTemplate<int>;
main.cpp
#include <iostream> #include "mytemplate.hpp" #include "notmain.hpp" int main() { std::cout << notmain() + MyTemplate<int>().f(1) << std::endl; }
notmain.cpp
#include "mytemplate.hpp" #include "notmain.hpp" int notmain() { return MyTemplate<int>().f(1); }
Kelemahan:
int
, tampaknya Anda terpaksa menambahkan penyertaan untuknya pada tajuk, deklarasi maju tidak cukup: templat eksternal & jenis tidak lengkap Ini meningkatkan ketergantungan tajuk sedikit.memindahkan definisi pada file cpp, biarkan hanya deklarasi di hpp, yaitu modifikasi contoh aslinya menjadi:
mytemplate.hpp
#ifndef MYTEMPLATE_HPP #define MYTEMPLATE_HPP template<class T> struct MyTemplate { T f(T t); }; #endif
mytemplate.cpp
#include "mytemplate.hpp" template<class T> T MyTemplate<T>::f(T t) { return t + 1; } // Explicit instantiation. template class MyTemplate<int>;
Kelemahan: proyek eksternal tidak dapat menggunakan templat Anda dengan tipenya sendiri. Anda juga dipaksa untuk membuat instance semua jenis secara eksplisit. Tapi mungkin ini adalah keuntungan karena programmer tidak akan lupa.
pertahankan definisi di hpp dan tambahkan
extern template
di setiap termasuk:mytemplate.cpp
#include "mytemplate.hpp" // Explicit instantiation. template class MyTemplate<int>;
main.cpp
#include <iostream> #include "mytemplate.hpp" #include "notmain.hpp" // extern template declaration extern template class MyTemplate<int>; int main() { std::cout << notmain() + MyTemplate<int>().f(1) << std::endl; }
notmain.cpp
#include "mytemplate.hpp" #include "notmain.hpp" // extern template declaration extern template class MyTemplate<int>; int notmain() { return MyTemplate<int>().f(1); }
Kelemahan: semua Include harus menambahkan
extern
file CPP mereka, yang mungkin akan dilupakan oleh programmer.Dengan salah satu solusi tersebut,
nm
sekarang berisi:notmain.o U MyTemplate<int>::f(int) main.o U MyTemplate<int>::f(int) mytemplate.o 0000000000000000 W MyTemplate<int>::f(int)
jadi kita lihat hanya
mytemplate.o
memiliki kompilasiMyTemplate<int>
sesuai yang diinginkan, sedangkannotmain.o
danmain.o
tidak karenaU
sarana tidak terdefinisi.Hapus definisi dari header yang disertakan tetapi juga tampilkan API eksternal pada template di pustaka khusus header
Jika pustaka Anda bukan hanya header,
extern template
metode ini akan berfungsi, karena menggunakan proyek hanya akan menautkan ke file objek Anda, yang akan berisi objek dari pembuatan templat eksplisit.Namun, untuk pustaka header saja, jika Anda ingin keduanya:
maka Anda dapat mencoba salah satu dari berikut ini:
mytemplate.hpp
: definisi templatemytemplate_interface.hpp
: deklarasi template hanya cocok dengan definisi darimytemplate_interface.hpp
, tanpa definisimytemplate.cpp
: Sertakanmytemplate.hpp
dan buat instan yang eksplisitmain.cpp
dan di mana pun di basis kode: sertakanmytemplate_interface.hpp
, bukanmytemplate.hpp
mytemplate.hpp
: definisi templatemytemplate_implementation.hpp
: menyertakanmytemplate.hpp
dan menambahextern
ke setiap kelas yang akan dipakaimytemplate.cpp
: Sertakanmytemplate.hpp
dan buat instan yang eksplisitmain.cpp
dan di mana pun di basis kode: sertakanmytemplate_implementation.hpp
, bukanmytemplate.hpp
Atau bahkan mungkin lebih baik untuk beberapa header: buat folder
intf
/impl
di dalamincludes/
folder Anda dan gunakanmytemplate.hpp
seperti namanya selalu.The
mytemplate_interface.hpp
pendekatan terlihat seperti ini:mytemplate.hpp
#ifndef MYTEMPLATE_HPP #define MYTEMPLATE_HPP #include "mytemplate_interface.hpp" template<class T> T MyTemplate<T>::f(T t) { return t + 1; } #endif
mytemplate_interface.hpp
#ifndef MYTEMPLATE_INTERFACE_HPP #define MYTEMPLATE_INTERFACE_HPP template<class T> struct MyTemplate { T f(T t); }; #endif
mytemplate.cpp
#include "mytemplate.hpp" // Explicit instantiation. template class MyTemplate<int>;
main.cpp
#include <iostream> #include "mytemplate_interface.hpp" int main() { std::cout << MyTemplate<int>().f(1) << std::endl; }
Kompilasi dan jalankan:
g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o mytemplate.o mytemplate.cpp g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o main.o main.cpp g++ -Wall -Wextra -std=c++11 -pedantic-errors -o main.out main.o mytemplate.o
Keluaran:
2
Diuji di Ubuntu 18.04.
C ++ 20 modul
https://en.cppreference.com/w/cpp/language/modules
Saya rasa fitur ini akan memberikan penyiapan terbaik ke depannya saat sudah tersedia, tetapi saya belum memeriksanya karena belum tersedia di GCC 9.2.1 saya.
Anda masih harus melakukan instantiation eksplisit untuk mempercepat / menghemat disk, tetapi setidaknya kita akan memiliki solusi yang masuk akal untuk "Hapus definisi dari header yang disertakan tetapi juga mengekspos template API eksternal" yang tidak memerlukan penyalinan sekitar 100 kali.
Penggunaan yang diharapkan (tanpa insansiasi eksplisit, tidak yakin seperti apa sintaks yang tepat, lihat: Bagaimana menggunakan template eksplisit instantiation dengan modul C ++ 20? )
helloworld.cpp
export module helloworld; // module declaration import <iostream>; // import declaration template<class T> export void hello(T t) { // export declaration std::cout << t << std::end; }
main.cpp
import helloworld; // import declaration int main() { hello(1); hello("world"); }
dan kemudian kompilasi disebutkan di https://quuxplusone.github.io/blog/2019/11/07/modular-hello-world/
clang++ -std=c++2a -c helloworld.cpp -Xclang -emit-module-interface -o helloworld.pcm clang++ -std=c++2a -c -o helloworld.o helloworld.cpp clang++ -std=c++2a -fprebuilt-module-path=. -o main.out main.cpp helloworld.o
Jadi dari sini kita melihat bahwa dentang dapat mengekstrak implementasi + antarmuka template ke dalam keajaiban
helloworld.pcm
, yang harus berisi beberapa representasi menengah LLVM dari sumber: Bagaimana template ditangani dalam sistem modul C ++? yang masih memungkinkan terjadinya spesifikasi template.Cara menganalisis build Anda dengan cepat untuk melihat apakah build tersebut akan mendapatkan banyak manfaat dari pembuatan template
Jadi, Anda memiliki proyek yang kompleks dan Anda ingin memutuskan apakah instantiasi template akan membawa keuntungan yang signifikan tanpa benar-benar melakukan refactor penuh?
Analisis di bawah ini dapat membantu Anda memutuskan, atau setidaknya memilih objek yang paling menjanjikan untuk difaktor ulang terlebih dahulu saat Anda bereksperimen, dengan meminjam beberapa ide dari: File objek C ++ saya terlalu besar
# List all weak symbols with size only, no address. find . -name '*.o' | xargs -I{} nm -C --size-sort --radix d '{}' | grep ' W ' > nm.log # Sort by symbol size. sort -k1 -n nm.log -o nm.sort.log # Get a repetition count. uniq -c nm.sort.log > nm.uniq.log # Find the most repeated/largest objects. sort -k1,2 -n nm.uniq.log -o nm.uniq.sort.log # Find the objects that would give you the most gain after refactor. # This gain is calculated as "(n_occurences - 1) * size" which is # the size you would gain for keeping just a single instance. # If you are going to refactor anything, you should start with the ones # at the bottom of this list. awk '{gain = ($1 - 1) * $2; print gain, $0}' nm.uniq.sort.log | sort -k1 -n > nm.gains.log # Total gain if you refactored everything. awk 'START{sum=0}{sum += $1}END{print sum}' nm.gains.log # Total size. The closer total gain above is to total size, the more # you would gain from the refactor. awk 'START{sum=0}{sum += $1}END{print sum}' nm.log
Mimpi: cache kompiler template
Saya pikir solusi utamanya adalah jika kita dapat membangun dengan:
g++ --template-cache myfile.o file1.cpp g++ --template-cache myfile.o file2.cpp
dan kemudian
myfile.o
secara otomatis akan menggunakan kembali template yang telah dikompilasi sebelumnya di seluruh file.Ini berarti 0 upaya ekstra pada pemrogram selain meneruskan opsi CLI ekstra itu ke sistem build Anda.
Bonus sekunder dari instansiasi template eksplisit: help IDE membuat daftar template
Saya telah menemukan bahwa beberapa IDE seperti Eclipse tidak dapat menyelesaikan "daftar semua contoh template yang digunakan".
Jadi misalnya, jika Anda berada di dalam kode templated, dan Anda ingin menemukan kemungkinan nilai template, Anda harus mencari penggunaan konstruktor satu per satu dan menyimpulkan jenis yang mungkin satu per satu.
Tetapi pada Eclipse 2020-03 saya dapat dengan mudah membuat daftar template yang dibuat secara eksplisit dengan melakukan pencarian Temukan semua penggunaan (Ctrl + Alt + G) pada nama kelas, yang menunjukkan saya misalnya dari:
template <class T> struct AnimalTemplate { T animal; AnimalTemplate(T animal) : animal(animal) {} std::string noise() { return animal.noise(); } };
untuk:
template class AnimalTemplate<Dog>;
Berikut demonya: https://github.com/cirosantilli/ide-test-projects/blob/e1c7c6634f2d5cdeafd2bdc79bcfbb2057cb04c4/cpp/animal_template.hpp#L15
Teknik guerrila lain yang dapat Anda gunakan di luar IDE adalah dengan menjalankan
nm -C
eksekusi terakhir dan grep nama template:yang secara langsung menunjuk pada fakta bahwa
Dog
itu adalah salah satu contoh:0000000000004dac W AnimalTemplate<Dog>::noise[abi:cxx11]() 0000000000004d82 W AnimalTemplate<Dog>::AnimalTemplate(Dog) 0000000000004d82 W AnimalTemplate<Dog>::AnimalTemplate(Dog)
sumber
Itu tergantung pada model kompiler - tampaknya ada model Borland dan model CFront. Dan kemudian itu juga tergantung pada niat Anda - jika Anda menulis perpustakaan, Anda mungkin (seperti yang disinggung di atas) secara eksplisit membuat contoh spesialisasi yang Anda inginkan.
Halaman GNU c ++ membahas model di sini https://gcc.gnu.org/onlinedocs/gcc-4.5.2/gcc/Template-Instantiation.html .
sumber