menggunakan template extern (C ++ 11)

116

Gambar 1: template fungsi

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){
   //...
}    
//explicit instantation
template void f<T>();

Main.cpp

#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
    f<char>();
    return 0;
}

Apakah ini cara yang benar untuk digunakan extern template, atau apakah saya menggunakan kata kunci ini hanya untuk templat kelas seperti pada Gambar 2?

Gambar 2: template kelas

TemplHeader.h

template<typename T>
class foo {
    T f();
};

TemplCpp.cpp

template<typename T>
void foo<T>::f() {
    //...
}
//explicit instantation
template class foo<int>;

Main.cpp

#include "TemplHeader.h"
extern template class foo<int>();
int main() {
    foo<int> test;
    return 0;
}

Saya tahu itu baik untuk meletakkan semua ini dalam satu file header, tetapi jika kita membuat contoh template dengan parameter yang sama di beberapa file, maka kita mendapat beberapa definisi yang sama dan kompiler akan menghapus semuanya (kecuali satu) untuk menghindari kesalahan. Bagaimana cara saya menggunakan extern template? Bisakah kita menggunakannya hanya untuk kelas, atau dapatkah kita menggunakannya untuk fungsi juga?

Selain itu, Gambar 1 dan Gambar 2 dapat diperluas menjadi solusi di mana templat berada dalam satu file header. Dalam hal ini, kita perlu menggunakan extern templatekata kunci untuk menghindari beberapa instan yang sama. Apakah ini hanya untuk kelas atau fungsi juga?

codekiddy
sumber
3
Ini sama sekali bukan penggunaan template eksternal yang benar ... ini bahkan tidak dapat dikompilasi
Dani
Bisakah Anda meluangkan waktu untuk menyusun (satu) pertanyaan dengan lebih jelas? Untuk apa Anda memposting kode? Saya tidak melihat pertanyaan terkait dengan itu. Juga, extern template class foo<int>();sepertinya ada kesalahan.
lihat
@Dani> dikompilasi dengan baik di studio visual saya 2010 kecuali pesan peringatan: Peringatan 1 peringatan C4231: ekstensi tidak standar digunakan: 'extern' sebelum template eksplisit instantiation
codekiddy
2
Pertanyaan @sehe sangat sederhana: bagaimana, dan kapan menggunakan kata kunci template eksternal? (template extern adalah C ++ 0x new future btw) Anda berkata "Selain itu, template extern class foo <int> (); sepertinya ada kesalahan." tidak, saya punya buku C ++ baru dan itu contoh dari buku saya.
codekiddy
1
@codekiddy: maka visual studio benar-benar bodoh .. di yang kedua prototipe tidak cocok dengan implementasinya, dan bahkan jika saya memperbaikinya dikatakan 'diharapkan unqualified-id' di dekat ()baris eksternal. baik buku dan studio visual Anda salah, coba gunakan kompiler yang lebih memenuhi standar seperti g ++ atau clang dan Anda akan melihat masalahnya.
Dani

Jawaban:

181

Anda sebaiknya hanya menggunakan extern templateuntuk memaksa compiler agar tidak membuat instance template saat Anda mengetahuinya bahwa itu akan dibuat di tempat lain. Ini digunakan untuk mengurangi waktu kompilasi dan ukuran file objek.

Sebagai contoh:

// header.h

template<typename T>
void ReallyBigFunction()
{
    // Body
}

// source1.cpp

#include "header.h"
void something1()
{
    ReallyBigFunction<int>();
}

// source2.cpp

#include "header.h"
void something2()
{
    ReallyBigFunction<int>();
}

Ini akan menghasilkan file objek berikut:

source1.o
    void something1()
    void ReallyBigFunction<int>()    // Compiled first time

source2.o
    void something2()
    void ReallyBigFunction<int>()    // Compiled second time

Jika kedua file dihubungkan bersama, salah satunya void ReallyBigFunction<int>()akan dibuang, mengakibatkan waktu kompilasi dan ukuran file objek terbuang percuma.

Agar tidak menyia-nyiakan waktu kompilasi dan ukuran file objek, ada externkata kunci yang membuat compiler tidak mengkompilasi fungsi template. Anda harus menggunakan ini jika dan hanya jika Anda tahu ini digunakan dalam biner yang sama di tempat lain.

Mengubah source2.cppke:

// source2.cpp

#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
    ReallyBigFunction<int>();
}

Akan menghasilkan file objek berikut:

source1.o
    void something1()
    void ReallyBigFunction<int>() // compiled just one time

source2.o
    void something2()
    // No ReallyBigFunction<int> here because of the extern

Ketika keduanya akan dihubungkan bersama, file objek kedua hanya akan menggunakan simbol dari file objek pertama. Tidak perlu membuang dan tidak membuang waktu kompilasi dan ukuran file objek.

Ini hanya boleh digunakan dalam proyek, seperti saat Anda menggunakan template seperti vector<int>beberapa kali, Anda harus menggunakan externsemua kecuali satu file sumber.

Ini juga berlaku untuk kelas dan fungsi sebagai satu, dan bahkan fungsi anggota template.

Dani
sumber
2
@ Codekiddy: Saya tidak tahu apa yang dimaksud Visual Studio dengan itu. Anda harus benar-benar menggunakan kompilator yang lebih patuh jika Anda ingin sebagian besar kode c ++ 11 berfungsi dengan benar.
Dani
4
@Dani: penjelasan terbaik dari template eksternal yang telah saya baca sejauh ini!
Pietro
90
"jika Anda tahu itu digunakan dalam biner yang sama di tempat lain.". Itu tidak cukup dan juga tidak diperlukan. Kode Anda "salah format, tidak diperlukan diagnostik". Anda tidak diperbolehkan untuk mengandalkan instansiasi implisit dari TU lain (kompilator diizinkan untuk mengoptimalkannya, seperti fungsi inline). Instansiasi eksplisit harus disediakan di TU lain.
Johannes Schaub - litb
32
Saya ingin menunjukkan bahwa jawaban ini mungkin salah, dan saya digigit olehnya. Untung saja komentar Johannes mendapat banyak suara positif dan kali ini saya lebih memperhatikannya. Saya hanya dapat berasumsi bahwa sebagian besar pemilih pada pertanyaan ini tidak benar-benar menerapkan jenis templat ini di beberapa unit kompilasi (seperti yang saya lakukan hari ini) ... Setidaknya untuk clang, satu-satunya cara yang pasti adalah dengan memasukkan definisi templat ini ke dalam tajukmu! Berhati-hatilah!
Steven Lu
6
@ JohannesSchaub-litb, dapatkah Anda menjelaskan lebih banyak atau mungkin memberikan jawaban yang lebih baik? Saya tidak yakin apakah saya benar-benar memahami keberatan Anda.
andreee
48

Wikipedia memiliki deskripsi terbaik

Di C ++ 03, kompilator harus membuat contoh template setiap kali template yang ditentukan secara lengkap ditemui di unit terjemahan. Jika templat dibuat dengan jenis yang sama di banyak unit terjemahan, ini dapat meningkatkan waktu kompilasi secara dramatis. Tidak ada cara untuk mencegah hal ini di C ++ 03, jadi C ++ 11 memperkenalkan deklarasi template eksternal, serupa dengan deklarasi data eksternal.

C ++ 03 memiliki sintaks ini untuk mewajibkan kompiler untuk membuat instance template:

  template class std::vector<MyClass>;

C ++ 11 sekarang menyediakan sintaks ini:

  extern template class std::vector<MyClass>;

yang memberi tahu kompiler untuk tidak membuat contoh template di unit terjemahan ini.

Peringatan: nonstandard extension used...

Microsoft VC ++ dulu memiliki versi non-standar dari fitur ini selama beberapa tahun (dalam C ++ 03). Kompilator memperingatkan tentang hal itu untuk mencegah masalah portabilitas dengan kode yang perlu dikompilasi pada kompiler yang berbeda juga.

Lihat contoh di halaman tertaut untuk melihat bahwa cara kerjanya kira-kira sama. Anda dapat mengharapkan pesan untuk pergi dengan versi masa depan dari MSVC, kecuali tentu saja ketika menggunakan lainnya ekstensi compiler non-standar pada waktu yang sama.

lihat
sumber
tnx untuk balasan Anda, jadi apa artinya acctualy ini bahwa "extern template" future berfungsi sepenuhnya untuk VS 2010 dan kita bisa mengabaikan peringatannya? (menggunakan pragma untuk mengabaikan pesan misalnya) dan pastikan bahwa template tidak dibuat lebih tepat waktu di VSC ++. penyusun. Terima kasih.
codekiddy
4
"... yang memberi tahu kompiler untuk tidak membuat contoh template di unit terjemahan ini ." Saya rasa ini tidak benar. Setiap metode yang didefinisikan dalam definisi kelas dihitung sebagai sebaris, jadi jika implementasi STL menggunakan metode sebaris untuk std::vector(cukup yakin semuanya melakukannya), externtidak akan berpengaruh.
Andreas Haferburg
Ya, jawaban ini menyesatkan. MSFT doc: "Kata kunci extern dalam spesialisasi hanya berlaku untuk fungsi anggota yang ditentukan di luar tubuh kelas. Fungsi yang didefinisikan di dalam deklarasi kelas dianggap sebagai fungsi inline dan selalu dipakai." Sayangnya, semua kelas STL di VS (terakhir diperiksa adalah 2017) hanya memiliki metode inline.
0kcat
Itu berlaku untuk semua deklarasi sebaris di mana pun mereka muncul, selalu @ 0kcats
lihat
@sehe Referensi ke Wiki dengan contoh std :: vector dan referensi ke MSVC dalam jawaban yang sama membuat orang percaya bahwa mungkin ada beberapa keuntungan dalam menggunakan std :: vector extern di MSVC, sementara sejauh ini tidak ada. Tidak yakin apakah ini adalah persyaratan standar, mungkin kompiler lain memiliki masalah yang sama.
0kcat
7

extern template hanya diperlukan jika deklarasi template selesai

Ini diisyaratkan dalam jawaban lain, tetapi menurut saya tidak cukup penekanan yang diberikan padanya.

Artinya, dalam contoh OP, extern templatetidak berpengaruh karena definisi templat pada tajuk tidak lengkap:

  • void f();: hanya deklarasi, tidak ada tubuh
  • class foo: mendeklarasikan metode f()tetapi tidak memiliki definisi

Jadi saya akan merekomendasikan hanya menghapus extern templatedefinisi dalam kasus khusus itu: Anda hanya perlu menambahkannya jika kelas sudah ditentukan sepenuhnya.

Sebagai contoh:

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){}

// Explicit instantiation for char.
template void f<char>();

Main.cpp

#include "TemplHeader.h"

// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?

int main() {
    f<char>();
    return 0;
}

kompilasi dan lihat simbol dengan nm:

g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f

keluaran:

TemplCpp.o
0000000000000000 W void f<char>()
Main.o
                 U void f<char>()

dan kemudian dari man nmkita melihat itu Uberarti tidak terdefinisi, jadi definisi itu tetap hanya TemplCppseperti yang diinginkan.

Semua ini bermuara pada tradeoff dari deklarasi header lengkap:

  • kelebihan:
    • memungkinkan kode eksternal untuk menggunakan template kami dengan tipe baru
    • kita memiliki opsi untuk tidak menambahkan contoh eksplisit jika kita baik-baik saja dengan objek mengasapi
  • kerugian:
    • saat mengembangkan kelas tersebut, perubahan implementasi header akan mengarahkan sistem build yang cerdas untuk membangun kembali semua termasuk, yang bisa jadi banyak file
    • jika kita ingin menghindari penggelembungan file objek, kita tidak hanya perlu melakukan instansiasi eksplisit (sama seperti dengan deklarasi header yang tidak lengkap) tetapi juga menambahkan extern templatedi every Includer, yang mungkin akan dilupakan oleh programmer.

Contoh lebih lanjut ditampilkan di: Instansiasi template eksplisit - kapan digunakan?

Karena waktu kompilasi sangat penting dalam proyek besar, saya akan sangat merekomendasikan deklarasi template yang tidak lengkap, kecuali pihak eksternal benar-benar perlu menggunakan kembali kode Anda dengan kelas kustom mereka sendiri yang kompleks.

Dan dalam hal ini, pertama-tama saya akan mencoba menggunakan polimorfisme untuk menghindari masalah waktu pembuatan, dan hanya menggunakan templat jika peningkatan kinerja yang nyata dapat dibuat.

Diuji di Ubuntu 18.04.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
4

Masalah yang diketahui dengan template adalah code bloating, yang merupakan konsekuensi dari pembuatan definisi kelas di setiap modul yang memanggil spesialisasi template kelas. Untuk mencegah hal ini, dimulai dengan C ++ 0x, seseorang dapat menggunakan kata kunci extern di depan spesialisasi template kelas

#include <MyClass>
extern template class CMyClass<int>;

Instan eksplisit dari kelas template harus terjadi hanya dalam satu unit terjemahan, lebih disukai yang memiliki definisi template (MyClass.cpp)

template class CMyClass<int>;
template class CMyClass<float>;
damirlj
sumber
0

Jika Anda telah menggunakan extern untuk fungsi sebelumnya, filosofi yang persis sama diikuti untuk template. jika tidak, pergi ke luar untuk fungsi sederhana dapat membantu. Selain itu, Anda mungkin ingin meletakkan extern di file header dan menyertakan header saat Anda membutuhkannya.

qqqqq
sumber