Pertanyaan Mengapa hanya template yang dapat diimplementasikan dalam file header?


Kutipan dari Pustaka standar C ++: tutorial dan buku pegangan:

Satu-satunya cara portabel menggunakan templat saat ini adalah untuk menerapkannya dalam file header dengan menggunakan fungsi inline.

Kenapa ini?

(Klarifikasi: file header bukan hanya solusi portabel. Tetapi mereka adalah solusi portabel yang paling nyaman.)


1383
2018-01-30 10:06


asal


Jawaban:


ini tidak diperlukan untuk menempatkan implementasi dalam file header, lihat solusi alternatif di akhir jawaban ini.

Bagaimanapun, alasan kode Anda gagal adalah bahwa, ketika instantiating template, compiler menciptakan kelas baru dengan argumen template yang diberikan. Sebagai contoh:

template<typename T>
struct Foo
{
    T bar;
    void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f; 

Ketika membaca baris ini, kompilator akan membuat kelas baru (sebut saja FooInt), yang setara dengan yang berikut:

struct FooInt
{
    int bar;
    void doSomething(int param) {/* do stuff using int */}
}

Oleh karena itu, compiler harus memiliki akses ke implementasi metode, untuk membiasakan mereka dengan argumen template (dalam hal ini int). Jika implementasi ini tidak di header, mereka tidak akan dapat diakses, dan oleh karena itu compiler tidak akan dapat instantiate template.

Solusi umum untuk ini adalah menulis deklarasi template dalam file header, kemudian mengimplementasikan kelas dalam file implementasi (misalnya .tpp), dan menyertakan file implementasi ini di ujung header.

// Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}

Dengan cara ini, implementasi masih terpisah dari deklarasi, tetapi dapat diakses oleh compiler.

Solusi lain adalah menjaga agar implementasi tetap terpisah, dan secara eksplisit memberi contoh semua contoh template yang Anda perlukan:

// Foo.h

// no implementation
template <typename T> struct Foo { ... };

//----------------------------------------    
// Foo.cpp

// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float

Jika penjelasan saya tidak cukup jelas, Anda dapat melihat pada C ++ Super-FAQ tentang hal ini.


1206
2018-01-30 10:26



Banyak jawaban yang benar di sini, tetapi saya ingin menambahkan ini (untuk kelengkapan):

Jika Anda, di bagian bawah file cpp implementasi, lakukan instantiasi eksplisit dari semua jenis template yang akan digunakan, linker akan dapat menemukannya seperti biasa.

Edit: Menambahkan contoh instantiasi template eksplisit. Digunakan setelah template telah ditentukan, dan semua fungsi anggota telah ditentukan.

template class vector<int>;

Ini akan memberi contoh (dan dengan demikian menyediakan linker) kelas dan semua fungsi anggotanya (hanya). Sintaks yang serupa berfungsi untuk fungsi template, jadi jika Anda memiliki operator non-anggota yang berlebihan, Anda mungkin perlu melakukan hal yang sama untuk itu.

Contoh di atas cukup tidak berguna karena vektor sepenuhnya didefinisikan di header, kecuali ketika file yang umum termasuk (header dikompilasi?) Menggunakan extern template class vector<int> sehingga membuatnya dari instantiating di semua lain (1000?) File yang menggunakan vektor.


200
2017-08-13 13:49



Ini karena persyaratan untuk kompilasi terpisah dan karena template adalah polimorfisme gaya instantiasi.

Mari kita sedikit lebih dekat ke beton untuk penjelasan. Katakanlah saya punya file-file berikut:

  • foo.h
    • menyatakan antarmuka class MyClass<T>
  • foo.cpp
    • mendefinisikan implementasi class MyClass<T>
  • bar.cpp
    • menggunakan MyClass<int>

Kompilasi terpisah berarti saya harus dapat mengkompilasi foo.cpp independen dari bar.cpp. Compiler melakukan semua kerja keras analisis, optimalisasi, dan pembuatan kode pada setiap unit kompilasi secara mandiri; kita tidak perlu melakukan analisis seluruh program. Hanya tautan yang perlu menangani seluruh program sekaligus, dan pekerjaan tautan itu jauh lebih mudah.

bar.cpp bahkan tidak perlu ada ketika saya mengkompilasi foo.cpp, tetapi saya harus tetap dapat menautkan foo.o Saya sudah bersama dengan bar.o Saya baru saja menghasilkan, tanpa perlu mengkompilasi ulang foo.cpp. foo.cpp bahkan bisa dikompilasi menjadi perpustakaan yang dinamis, didistribusikan di tempat lain tanpa foo.cpp, dan dikaitkan dengan kode yang mereka tulis beberapa tahun setelah saya menulis foo.cpp.

"Instansiasi-gaya polimorfisme" berarti bahwa template MyClass<T> sebenarnya bukan kelas generik yang dapat dikompilasi ke kode yang dapat bekerja untuk nilai apa pun T. Itu akan menambah overhead seperti tinju, perlu untuk melewati pointer fungsi ke pengalokasi dan konstruktor, dll. Tujuan dari C ++ template adalah untuk menghindari menulis hampir identik class MyClass_int, class MyClass_float, dll, tetapi masih bisa berakhir dengan kode yang dikompilasi yang sebagian besar seolah-olah kita punya menulis setiap versi secara terpisah. Jadi template secara harfiah sebuah template; template kelas tidak kelas, itu adalah resep untuk membuat kelas baru untuk masing-masing T kita jumpai. Template tidak dapat dikompilasi menjadi kode, hanya hasil instantiate template yang dapat dikompilasi.

Jadi ketika foo.cpp dikompilasi, kompiler tidak bisa melihat bar.cpp untuk mengetahuinya MyClass<int> diperlukan. Itu bisa melihat template MyClass<T>, tetapi tidak dapat memancarkan kode untuk itu (ini adalah template, bukan kelas). Dan kapan bar.cpp dikompilasi, kompilator dapat melihat bahwa ia perlu membuat MyClass<int>, tetapi tidak bisa melihat template MyClass<T> (hanya antarmukanya foo.h) sehingga tidak dapat membuatnya.

Jika foo.cpp itu sendiri digunakan MyClass<int>, maka kode untuk itu akan dihasilkan saat kompilasi foo.cpp, jadi ketika bar.o terkait dengan foo.o mereka dapat dihubungkan dan akan bekerja. Kita bisa menggunakan fakta itu untuk mengijinkan seperangkat terbatas dari template instantiations untuk diimplementasikan dalam file .cpp dengan menulis satu template. Tapi tidak ada jalan untuk itu bar.cpp untuk menggunakan templat sebagai template dan instantiate pada jenis apa pun yang disukai; itu hanya dapat menggunakan versi templated kelas yang sudah ada sebelumnya yang dibuat oleh penulis foo.cpp berpikir untuk menyediakan.

Anda mungkin berpikir bahwa ketika mengkompilasi template, kompilator harus "menghasilkan semua versi", dengan yang tidak pernah digunakan disaring selama menghubungkan. Selain dari overhead besar dan kesulitan ekstrim seperti pendekatan yang akan dihadapi karena fitur "pengubah tipe" seperti pointer dan array memungkinkan bahkan hanya tipe bawaan untuk menghasilkan jumlah jenis yang tak terbatas, apa yang terjadi ketika saya memperpanjang program saya dengan menambahkan:

  • baz.cpp
    • menyatakan dan mengimplementasikan class BazPrivate, dan menggunakan MyClass<BazPrivate>

Tidak mungkin cara ini bisa berhasil kecuali kita juga

  1. Harus mengkompilasi ulang foo.cpp setiap kali kita berubah file lain dalam program, dalam hal itu ditambahkan sebuah novel baru instantiation of MyClass<T>
  2. Wajibkan itu baz.cpp contains (mungkin via header include) template lengkap dari MyClass<T>, sehingga kompilator dapat menghasilkan MyClass<BazPrivate> selama kompilasi baz.cpp.

Tidak ada yang suka (1), karena sistem kompilasi seluruh program-analisis mengambil selama-lamanya untuk mengkompilasi, dan karena itu membuat tidak mungkin untuk mendistribusikan pustaka terkompilasi tanpa kode sumber. Jadi kita punya (2) sebagai gantinya.


173
2018-05-11 03:54



Templat perlu dipakai oleh kompilator sebelum benar-benar mengkompilasinya ke dalam kode objek. Instansiasi ini hanya dapat dicapai jika argumen template diketahui. Sekarang bayangkan skenario di mana fungsi template dideklarasikan a.h, didefinisikan dalam a.cpp dan digunakan dalam b.cpp. Kapan a.cpp dikompilasi, belum tentu diketahui kompilasi yang akan datang b.cpp akan membutuhkan instance dari template, apalagi contoh spesifik apa yang akan terjadi. Untuk lebih banyak header dan file sumber, situasinya dapat dengan cepat menjadi lebih rumit.

Seseorang dapat menyatakan bahwa kompiler dapat dibuat lebih pintar untuk "melihat ke depan" untuk semua penggunaan template, tapi saya yakin bahwa tidak akan sulit untuk membuat skenario rekursif atau yang rumit. AFAIK, kompiler tidak melakukan hal tersebut di depan. Seperti yang ditunjukkan oleh Anton, beberapa compiler mendukung deklarasi ekspor eksplisit dari instantiations template, tetapi tidak semua compiler mendukungnya (belum?).


64
2018-01-30 10:23



Sebenarnya, versi standar C ++ sebelum C ++ 11 mendefinisikan kata kunci 'ekspor', itu akan memungkinkan untuk mendeklarasikan template dalam file header dan menerapkannya di tempat lain.

Sayangnya, tidak satu pun dari compiler populer yang mengimplementasikan kata kunci ini. Satu-satunya yang saya ketahui adalah frontend yang ditulis oleh Edison Design Group, yang digunakan oleh kompiler Comeau C ++. Semua yang lain memaksa Anda menulis template dalam file header, yang membutuhkan definisi kode untuk instantiasi yang tepat (seperti yang telah disebutkan orang lain).

Akibatnya, komite standar ISO C ++ memutuskan untuk menghapus export fitur template yang dimulai dengan C ++ 11.


47
2018-01-30 13:38



Meskipun standar C ++ tidak memiliki persyaratan seperti itu, beberapa compiler mengharuskan semua fungsi dan template kelas harus tersedia di setiap unit terjemahan yang digunakan. Akibatnya, bagi para penyusun itu, tubuh fungsi template harus tersedia dalam file header. Untuk mengulang: itu berarti kompiler tidak akan memungkinkan mereka untuk didefinisikan dalam file non-header seperti file .cpp

Ada sebuah ekspor kata kunci yang seharusnya meredakan masalah ini, tetapi tidak ada yang mendekati portabel.


31
2018-01-30 10:15



Templat harus digunakan dalam header karena compiler harus menginstansi versi yang berbeda dari kode, tergantung pada parameter yang diberikan / dideduksi untuk parameter template. Ingat bahwa template tidak mewakili kode secara langsung, tetapi template untuk beberapa versi kode itu. Ketika Anda meng-compile fungsi non-template dalam .cppfile, Anda menyusun fungsi / kelas konkret. Ini bukan kasus untuk template, yang dapat dipakai dengan jenis yang berbeda, yaitu, kode beton harus dipancarkan ketika mengganti parameter template dengan jenis beton.

Ada fitur dengan export kata kunci yang dimaksudkan untuk digunakan untuk kompilasi terpisah. Itu export fitur tidak lagi digunakan di C++11 dan, AFAIK, hanya satu kompilator yang mengimplementasikannya. Anda seharusnya tidak menggunakan export. Kompilasi terpisah tidak dimungkinkan di C++ atau C++11 tapi mungkin masuk C++17, jika konsep dibuat, kita bisa memiliki beberapa cara kompilasi terpisah.

Untuk kompilasi terpisah yang harus dicapai, pengecekan badan template terpisah harus dimungkinkan. Tampaknya solusi itu mungkin dengan konsep. Lihatlah ini kertas baru-baru ini disajikan di rapat komite standar. Saya pikir ini bukan satu-satunya persyaratan, karena Anda masih perlu memberi kode untuk kode template di kode pengguna.

Masalah kompilasi terpisah untuk template Saya kira itu juga masalah yang timbul dengan migrasi ke modul, yang saat ini sedang dikerjakan.


26
2018-05-12 16:42



Ini berarti bahwa cara paling mudah untuk menentukan implementasi metode kelas template adalah mendefinisikannya di dalam definisi kelas template.

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};

13
2018-01-30 10:53