Şablonlar neden yalnızca başlık dosyasında uygulanabilir?


1777

C ++ standart kütüphanesinden alıntı : bir eğitim ve el kitabı :

Şu anda şablonları kullanmanın tek taşınabilir yolu, bunları satır içi işlevler kullanarak başlık dosyalarına uygulamaktır.

Bu neden?

(Açıklama: başlık dosyaları tek taşınabilir çözüm değildir. Ancak en uygun taşınabilir çözümdür.)


13
Tüm şablon işlev tanımlarının başlık dosyasına yerleştirilmesinin, muhtemelen bunları kullanmanın en uygun yolu olduğu doğru olsa da, bu alıntıda "satır içi" nin ne yaptığı hala açık değildir. Bunun için satır içi işlevler kullanmaya gerek yoktur. "Inline" ın bununla hiçbir ilgisi yoktur.
AnT

7
Kitap güncel değil.
gerardw

1
Şablon, bayt kodunda derlenebilen bir işlev gibi değildir. Böyle bir işlevi üretmek için sadece bir örüntüdür. Bir şablonu kendi başına * .cpp dosyasına koyarsanız, derlenecek bir şey yoktur. Ayrıca, açık örnekleme aslında bir şablon değil, * .obj dosyasında sona eren şablondan bir işlev yapmak için başlangıç ​​noktasıdır.
1919'da

5
Bu nedenle şablon kavramının C ++ ile sakatlandığını hisseden tek kişi ben miyim? ...
DragonGamer

Yanıtlar:


1558

Uyarı: Öyle değil , başlık dosyasına uygulanmasını koymak bu cevabın sonunda alternatif bir çözüm görmek için gerekli.

Her neyse, kodunuzun başarısız olmasının nedeni, bir şablonu başlatırken derleyicinin verilen şablon bağımsız değişkeniyle yeni bir sınıf oluşturmasıdır. Örneğin:

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

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

Bu satırı okurken, derleyici FooIntaşağıdakilere eşdeğer olan yeni bir sınıf yaratacaktır (diyelim ki ):

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

Sonuç olarak, derleyicinin, şablon argümanı ile (bu örnekte int) somutlaştırılması için yöntemlerin uygulanmasına erişimi olmalıdır . Bu uygulamalar başlıkta olmasaydı erişilemezlerdi ve bu nedenle derleyici şablonu başlatamazdı.

Bunun yaygın bir çözümü, şablon bildirimini bir başlık dosyasına yazmak, ardından sınıfı bir uygulama dosyasına (örneğin .tpp) uygulamak ve bu uygulama dosyasını başlığın sonuna dahil etmektir.

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
}

Bu şekilde, uygulama yine deklarasyondan ayrılır, ancak derleyici tarafından erişilebilir.

Alternatif çözüm

Başka bir çözüm, uygulamayı ayrı tutmak ve ihtiyacınız olan tüm şablon örneklerini açıkça somutlaştırmaktır:

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

Açıklamam yeterince açık değilse, bu konudaki C ++ Süper SSS'ye göz atabilirsiniz .


96
Aslında, açık örneklemenin başlıktan ziyade Foo'nun tüm üye işlevlerinin tanımlarına erişimi olan bir .cpp dosyasında olması gerekir.
Mankarse

11
"derleyicinin, şablon argümanı ile (bu örnekte int) örneklerin uygulanması için yöntemlerin uygulanmasına erişimi olması gerekir. Bu uygulamalar başlıkta olmasaydı, erişilemezdi" Ama neden bir uygulama .cpp dosyası derleyiciye erişilemiyor mu? Bir derleyici .cpp bilgilerine de erişebilir, bunları nasıl .obj dosyalarına dönüştürür? EDIT: Bu soruya cevap bu cevap verilen bağlantıda ...
xcrypt

31
Bunun açık bir şekilde, anahtar şeyin bu yazıda bahsedilmeyen derleme birimi ile ilgili olduğu açıklamasını düşünmüyorum
zinking

6
@Gabson: yapılar ve sınıflar, sınıflar için varsayılan erişim değiştiricisinin "private" olması dışında, yapılar için herkese açıktır. Bu soruya bakarak öğrenebileceğiniz başka küçük farklılıklar da var .
Luc Touraille

3
Sorunun yanlış bir önermeye dayandığını açıklığa kavuşturmak için bu cevabın en başına bir cümle ekledim. Birisi "X neden doğrudur?" Aslında X doğru olmadığında, bu varsayımı hızla reddetmeliyiz.
Aaron McDaid

250

Burada çok doğru cevaplar, ama bunu eklemek istedim (bütünlük için):

Uygulama cpp dosyasının altında, şablonun kullanılacağı tüm türlerin açık bir örneğini yaparsanız, bağlayıcı bunları her zamanki gibi bulabilir.

Düzenleme: Açık şablon örneği örneği ekleme. Şablon tanımlandıktan ve tüm üye işlevler tanımlandıktan sonra kullanılır.

template class vector<int>;

Bu, sınıfı ve tüm üye işlevlerini (yalnızca) başlatır (ve böylece bağlayıcıya sunar). Benzer sözdizimi şablon işlevleri için de çalışır, bu nedenle üye olmayan operatör aşırı yüklemeleriniz varsa, bunlar için de aynı işlemi yapmanız gerekebilir.

Yukarıdaki örnek oldukça işe yaramaz çünkü vektör, üstbilgilerde tam olarak tanımlandığı için, ortak bir içerme dosyası (önceden derlenmiş üstbilgi?) Vektörü kullanan diğerextern template class vector<int> tüm (1000?) Dosyalarda örneklemesini engellemek için kullanırsa.


51
Ugh. İyi cevap, ama gerçek temiz bir çözüm yok. Bir şablon için tüm olası türleri listelemek, bir şablonun olması gerektiği gibi görünmüyor.
Jiminion

6
Bu birçok durumda iyi olabilir, ancak genellikle sınıfı typeel ile listelemeden herhangi biriyle kullanmanıza izin veren şablonun amacını ihlal eder.
Tomáš Zato - Monica'yı

7
vectoriyi bir örnek değildir, çünkü bir kap kendiliğinden "tüm" türlerini hedefler. Ancak sık sık, yalnızca belirli bir tür küme için, örneğin sayısal türler için tasarlanmış şablonlar oluşturduğunuz sık sık meydana gelir: int8_t, int16_t, int32_t, uint8_t, uint16_t, vb. Bu durumda, bir şablon kullanmak yine de mantıklıdır , ancak bunları tüm türler için açıkça somutlaştırmak da mümkündür ve bence tavsiye edilir.
UncleZeiv

Şablon tanımlandıktan ve "tüm üye işlevleri tanımlandıktan" sonra kullanılır. Teşekkürler !
Vitt Volt

1
Bir şey eksik gibi hissediyorum ... İki tür için açık örnekleme sınıfın .cppdosyaya koymak ve iki örnek diğer .cppdosyalardan sevk edilir ve yine de üyelerin bulunma bağlantı hatası alıyorum.
oarfish

250

Bunun nedeni ayrı derleme gereksinimi ve şablonların örnekleme tarzı çok biçimlilik olmasıdır.

Bir açıklama için betona biraz daha yaklaşalım. Aşağıdaki dosyaları aldığımı söyle:

  • foo.h
    • arayüzünü class MyClass<T>
  • foo.cpp
    • uygulanmasını tanımlar class MyClass<T>
  • bar.cpp
    • kullanımları MyClass<int>

Ayrı derleme , bar.cpp'den bağımsız olarak foo.cpp dosyasını derleyebilmem gerektiği anlamına gelir . Derleyici, her derleme birimindeki tüm analiz, optimizasyon ve kod oluşturma çalışmalarını tamamen bağımsız olarak yapar; tam program analizi yapmamız gerekmez. Tüm programı bir kerede işlemesi gereken sadece bağlayıcıdır ve bağlayıcının işi oldukça kolaydır.

bar.cpp bile derleme yaparken mevcut gerekmez foo.cpp , ama yine de bağlamak mümkün olmalıdır foo.o Zaten birlikte vardı bar.o yeniden derleme gerek kalmadan, az önce üretilen ettik foo .cpp . foo.cpp , dinamik bir kütüphaneye bile derlenebilir, foo.cpp olmadan başka bir yere dağıtılabilir ve foo.cpp yazdıktan yıllar sonra yazdıkları kodlarla bağlantılı olabilir .

"Örnekleme tarzı polimorfizm", şablonun MyClass<T>herhangi bir değeri için çalışabilecek kod için derlenebilecek genel bir sınıf olmadığı anlamına gelir T. Yani vb allocators ve kurucular, C ++ şablonları niyeti yazmak zorunda kalmamak için işlev işaretçileri geçmesi gerek, boks gibi havai böyle eklersiniz neredeyse özdeş class MyClass_int, class MyClass_floatvb ama hala derlenmiş kod ile bitirmek muktedir çoğunlukla sanki biz vardı ayrı ayrı versiyonunu yazılı. Yani bir şablon tam anlamıyla bir şablondur; sınıf şablonu bir sınıf değildir , Tkarşılaştığımız her biri için yeni bir sınıf oluşturmak için bir reçetedir . Bir şablon kodda derlenemez, sadece şablonun başlatılmasının sonucu derlenebilir.

Yani foo.cpp derlendiğinde, derleyici bunun gerekli olduğunu bilmek için bar.cpp dosyasını göremezMyClass<int> . Şablonu görebilir MyClass<T>, ancak bunun için kod yayınlayamaz (bir şablon değil, bir sınıftır). Ve bar.cpp derlendiğinde, derleyici bir oluşturması gerektiğini görebilir MyClass<int>, ancak şablonu göremez MyClass<T>(yalnızca foo.h içindeki arayüzü ).

Eğer foo.cpp kendisi kullanıyorsa MyClass<int>, o zaman kod foo.cpp derlenirken üretilecektir , bu yüzden bar.o foo.o ile bağlantı kurduğunda bağlanabilir ve çalışacaktır. Bu gerçeği, tek bir şablon yazarak .cpp dosyasında sonlu bir şablon örneği kümesinin uygulanmasına izin vermek için kullanabiliriz. Ancak bar.cpp'in şablonu şablon olarak kullanmasının ve sevdiği türlere örnek oluşturmasının bir yolu yoktur ; yalnızca foo.cpp yazarının sağlamayı düşündüğü şablonlu sınıfın önceden var olan sürümlerini kullanabilir .

Bir şablonu derlerken derleyicinin "tüm sürümleri oluşturması" gerektiğini ve bağlantıda hiç kullanılmamış olanların filtrelenmesi gerektiğini düşünebilirsiniz. İşaretçi ve diziler gibi "tip değiştirici" özellikleri sadece yerleşik tiplerin bile sonsuz sayıda türe yol açmasına izin verdiğinden, bu tür bir yaklaşımın karşı karşıya olduğu devasa zorluklar ve aşırı zorlukların yanı sıra, şimdi programımı genişlettiğimde ne olacak toplayarak:

  • baz.cpp
    • beyan ve uygular class BazPrivateve kullanırMyClass<BazPrivate>

Ya biz

  1. Yeni bir roman örneği eklenmesi durumunda , programdaki başka bir dosyayı her değiştirdiğimizde foo.cpp'yi yeniden derlemeliyizMyClass<T>
  2. Gerektir baz.cpp (başlık, muhtemelen ile) içeren tam bir şablonu MyClass<T>derleyici üretebilir, böylece, MyClass<BazPrivate>derlenmesi esnasında baz.cpp .

Kimse (1) 'i sevmez, çünkü tüm program analizi derleme sistemleri derlenmeyi sonsuza dek alır ve derlenmiş kitaplıkların kaynak kodu olmadan dağıtılmasını imkansız hale getirir. Onun yerine (2) var.


50
bir şablonun tam anlamıyla bir şablon olduğunu
v.oddou

Bilmek istiyorum, sınıf üstbilgisi veya kaynak dosya dışında bir yerden açık örneklemeler yapmak mümkün mü? Örneğin, main.cpp dosyasında mı?
09:00

1
@Birger Tam şablon uygulamasına erişimi olan herhangi bir dosyadan (aynı dosyada veya başlık içerdiği için) yapabilmeniz gerekir.
Ben

11
@ajeh Bu söylem değil. Soru, "neden bir başlıkta şablonlar uygulamak zorundasınız?" Sorusudur, bu yüzden C ++ dilinin bu gereksinimi sağlayan teknik seçimleri açıkladım. Cevabımı yazmadan önce, diğerleri tam çözüm olmayan geçici çözümler sağladılar, çünkü tam bir çözüm olamaz . Bu cevapların sorunun "neden" açısının daha ayrıntılı tartışılmasıyla tamamlanacağını hissettim.
Ben

1
düşünün bu şekilde millet ... eğer şablonları kullanmıyorsanız (ihtiyacınız olanı verimli bir şekilde kodlamak için), yine de o sınıfın sadece birkaç versiyonunu sunuyorsunuz. 3 seçeneğiniz var. 1). şablon kullanmayın. (diğer tüm sınıflar / işlevler gibi, kimse başkalarının türleri değiştirememesi umurunda değildir) 2). şablonları kullanabilir ve hangi türleri kullanabileceğini belgeleyebilir. 3). onlara tüm uygulama (kaynak) bonusunu verin 4). Sınıflarınızdan başka bir şablon oluşturmak istediklerinde onlara tüm kaynağı verin;)
Puddle

81

Şablonların gerçekte nesne koduna derlenmeden önce derleyici tarafından başlatılması gerekir . Bu örnekleme ancak şablon argümanları biliniyorsa elde edilebilir. Şimdi bir şablon işlevinin bildirildiği a.h, tanımlandığı a.cppve kullanıldığı bir senaryo düşünün b.cpp. Ne zaman a.cppderlenmiş, mutlaka yaklaşan derleme olduğu bilinmemektedir b.cpp, şablonun bir örneğini gerektirdiğini olacağını belirli örneği şöyle dursun edecektir. Daha fazla başlık ve kaynak dosyası için durum hızla daha karmaşık hale gelebilir.

Bir derleyici şablonun tüm kullanımları için "ileriye bakmak" için daha akıllı yapılabilir iddia edilebilir, ancak eminim özyinelemeli veya başka türlü karmaşık senaryolar oluşturmak zor olmayacaktır. AFAIK, derleyiciler böyle görünmüyor. Anton'un işaret ettiği gibi, bazı derleyiciler şablon örneklemelerinin açık ihracat bildirimlerini destekler, ancak tüm derleyiciler bunu desteklemez (henüz?).


1
"dışa aktarma" standarttır, ancak derleyici ekiplerinin çoğunun henüz yapmadığı için uygulaması zordur.
vava

5
dışa aktarma kaynak açıklama ihtiyacını ortadan kaldırmaz ve derleyici bağımlılıklarını azaltırken, derleyici üreticilerinden büyük bir çaba gerektirir. Herb Sutter'in kendisi derleyici üreticilerinden ihracatı 'unutmasını' istedi. Gereken zaman yatırımı başka bir yerde daha iyi harcayacağından ...
Pieter

2
Dolayısıyla ihracatın henüz 'uygulanmadığını' sanmıyorum. Diğerleri ne kadar sürdüğünü ve ne kadar az kazanıldığını gördükten sonra muhtemelen EDG'den başka hiç kimse tarafından yapılmayacak
Pieter

3
Bu ilginizi çekiyorsa, makaleye "Neden ihracat yapamayız" denir, blogunda listelenmiştir ( gotw.ca/publications ) ama orada pdf yok (hızlı bir google bunu açmalıdır )
Pieter

1
Tamam, iyi örnek ve açıklama için teşekkürler. Ama benim sorum şu: neden derleyici şablonun nerede çağrıldığını anlayamıyor ve tanım dosyasını derlemeden önce bu dosyaları derleyemiyor? Basit bir durumda yapılabileceğini hayal edebiliyorum ... Karşılıklı bağımlılıkların düzeni hızlı bir şekilde bozacağı mı?
Vlad

62

Aslında C ++ 11 öncesinde standart tanımlı exportanahtar kelimeyi olur mümkün bir başlık dosyasına şablonları ilan etmek yapmak ve başka bir yerde bunları uygulamak.

Popüler derleyicilerden hiçbiri bu anahtar kelimeyi uygulamadı. Bildiğim tek kişi, Comeau C ++ derleyicisi tarafından kullanılan Edison Tasarım Grubu tarafından yazılmış ön uç. Tüm diğerleri, başlık dosyalarına şablon yazmanızı istedi, çünkü derleyicinin uygun örnekleme için şablon tanımına ihtiyacı var (diğerleri zaten belirtildiği gibi).

Sonuç olarak, ISO C ++ standart komitesi exportC ++ 11 ile şablonların özelliğini kaldırmaya karar verdi .


6
... ve birkaç yıl sonra, nihayet anladım exportaslında olurdu verilen bizi ve ne değildir ... ve şimdi yürekten EDG insanlarla anlaşmak: Bu getirdiler olmazdı neyi '11 kendimi çoğu insan ( dahil) düşünürsünüz ve C ++ standardı onsuz daha iyidir.
DevSolar

4
@DevSolar: Bu makale politik, tekrarlayıcı ve kötü yazılmış. bu normal standart düzyazı değildir. Beklenmedik bir şekilde uzun ve sıkıcı, temelde onlarca sayfa boyunca aynı şeyleri 3 kat söyleyerek. Ama şimdi ihracatın ihracat olmadığı konusunda bilgilendirildim. Bu iyi bir istihbarat!
v.oddou

1
@ v.oddou: İyi geliştirici ve iyi teknik yazar iki ayrı beceridir. Bazıları her ikisini de yapabilir, çoğu yapamaz. ;-)
DevSolar

@ v.oddou Kağıt sadece kötü yazılmış değil, dezenformasyon. Ayrıca bu gerçekliğin bir dönüşü: aslında ihracat için son derece güçlü argümanlar, ihracata karşı olduğu gibi görünecek şekilde karıştırılıyor: “ihracatın varlığında standartta çok sayıda ODRrelated delik bulunması. Dışa aktarmadan önce, ODR ihlallerinin derleyici tarafından teşhis edilmesi gerekmiyordu. Şimdi gerekli çünkü farklı çeviri birimlerinden gelen iç veri yapılarını birleştirmeniz gerekiyor ve aslında farklı şeyleri temsil ediyorsa bunları birleştiremezsiniz, bu yüzden kontrolü yapmanız gerekir. ”
curiousguy

" Şimdi gerçekleştiği sırada hangi çeviri birimini eklemesi gerekiyor " Duh. Topal olan argümanları kullanmak zorunda kaldığınızda, hiçbir argümanınız yoktur. Elbette hatalarınızdaki dosya adlarından bahsedeceksiniz, anlaşma nedir? Herkes BS için düşüyor zihin boggling olduğunu. " James Kanze gibi uzmanlar bile ihracatın gerçekten böyle olduğunu kabul etmekte zorlanıyorlar. " NE? !!!!
curiousguy

34

Standart C ++ 'ın böyle bir gereksinimi olmamasına rağmen, bazı derleyiciler tüm işlev ve sınıf şablonlarının kullanıldıkları her çeviri biriminde kullanılabilir olmasını gerektirir. Aslında, bu derleyiciler için şablon işlevlerinin gövdeleri bir başlık dosyasında kullanılabilir olmalıdır. Tekrarlamak gerekirse: bu derleyicilerin .cpp dosyaları gibi başlık dışı dosyalarda tanımlanmasına izin vermeyeceği anlamına gelir

Bu sorunu hafifletmesi beklenen bir dışa aktarma anahtar sözcüğü var, ancak taşınabilir olmaya yakın bir yerde değil.


Neden onları "satır içi" anahtar kelimesi ile .cpp dosyasına uygulayamıyorum?
MainID

2
Yapabilirsin ve hatta "satıriçi" koymak zorunda değilsin. Ancak bunları sadece o cpp dosyasında ve başka hiçbir yerde kullanamazsınız.
vava

10
Bu hemen hemen en doğru yanıttır, ancak "bu derleyicilerin .cpp dosyaları gibi başlık dışı dosyalarda tanımlanmasına izin vermeyeceği anlamına gelir" patenti yanlıştır.
Yörüngedeki Hafiflik Yarışları

28

Şablonlar başlıklarda kullanılmalıdır, çünkü derleyici şablon parametreleri için verilen / çıkarılan parametrelere bağlı olarak kodun farklı sürümlerini somutlaştırmalıdır. Bir şablonun kodu doğrudan temsil etmediğini, ancak bu kodun çeşitli sürümleri için bir şablon olduğunu unutmayın. .cppDosyada şablon olmayan bir işlev derlediğinizde, somut bir işlev / sınıf derlersiniz . Farklı türlerle somutlaştırılabilecek şablonlar için durum böyle değildir, yani şablon parametreleri somut türlerle değiştirilirken somut kod çıkarılmalıdır.

exportAnahtar kelimede ayrı derleme için kullanılması gereken bir özellik vardı . Bu exportözellik kullanımdan kaldırılmıştır C++11ve AFAIK, yalnızca bir derleyici uygulamıştır. Kullanmamalısınız export. Ayrı derleme mümkün değildir C++ya C++11ama belki de C++17kavramlar bunu yaparsanız, bu ayrı derleme bir yol olabilir.

Ayrı bir derlemenin gerçekleştirilmesi için ayrı şablon gövdesi kontrolü mümkün olmalıdır. Kavramlarla bir çözüm mümkün görünüyor. Son zamanlarda standart komite toplantısında sunulan bu makaleye bir göz atın . Hala kullanıcı kodu şablon kodu için kod somutlaştırmak gerekir, çünkü bu tek gereklilik olduğunu düşünüyorum.

Şablonlar için ayrı derleme sorunu Sanırım şu anda çalışmakta olan modüllere geçiş ile ilgili bir sorun.


15

Bu, şablon sınıflarının yöntem uygulamalarını tanımlamanın en taşınabilir yolunun bunları şablon sınıfı tanımının içinde tanımlamak olduğu anlamına gelir.

template < typename ... >
class MyClass
{

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

15

Yukarıda birçok iyi açıklama olmasına rağmen, şablonları başlık ve gövdeye ayırmanın pratik bir yolunu kaçırıyorum.
Ana kaygım, tanımını değiştirdiğimde tüm şablon kullanıcılarının yeniden derlenmesinden kaçınmak.
Şablon gövdesindeki tüm şablon örneklerine sahip olmak benim için uygun bir çözüm değildir, çünkü şablon yazarı kullanımının ve şablon kullanıcısının bunu değiştirme hakkına sahip olup olmadığını bilmeyebilir.
Daha eski derleyiciler için de geçerli olan aşağıdaki yaklaşımı kullandım (gcc 4.3.4, aCC A.03.13).

Her şablon kullanımı için kendi başlık dosyasında (UML modelinden oluşturulan) bir typedef vardır. Gövdesi, (sonunda bağlanmış bir kütüphanede sonuçlanan) örneklemeyi içerir.
Şablonun her kullanıcısı bu başlık dosyasını içerir ve typedef kullanır.

Şematik bir örnek:

MyTemplate.h:

#ifndef MyTemplate_h
#define MyTemplate_h 1

template <class T>
class MyTemplate
{
public:
  MyTemplate(const T& rt);
  void dump();
  T t;
};

#endif

MyTemplate.cpp:

#include "MyTemplate.h"
#include <iostream>

template <class T>
MyTemplate<T>::MyTemplate(const T& rt)
: t(rt)
{
}

template <class T>
void MyTemplate<T>::dump()
{
  cerr << t << endl;
}

MyInstantiatedTemplate.h:

#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"

typedef MyTemplate< int > MyInstantiatedTemplate;

#endif

MyInstantiatedTemplate.cpp:

#include "MyTemplate.cpp"

template class MyTemplate< int >;

main.cpp:

#include "MyInstantiatedTemplate.h"

int main()
{
  MyInstantiatedTemplate m(100);
  m.dump();
  return 0;
}

Bu şekilde, tüm şablon kullanıcılarının (ve bağımlılıkların) değil, yalnızca şablon örneklerinin yeniden derlenmesi gerekir.


1
MyInstantiatedTemplate.hDosya ve eklenen MyInstantiatedTemplatetürü hariç bu yaklaşımı seviyorum . Bunu kullanmazsan biraz daha temiz, imho. Bunu gösteren farklı bir soruya cevabımı kontrol et: stackoverflow.com/a/41292751/4612476
Cameron Tacklind

Bu iki dünyanın en iyisini alır. Bu cevabın daha yüksek derecelendirilmesini dilerdim! Aynı fikrin biraz daha net bir şekilde uygulanması için yukarıdaki bağlantıya da bakın.
Wormer

8

Burada kayda değer bir şey eklemek için. Bir şablon şablonu, işlev şablonları olmadığında uygulama dosyasında gayet iyi tanımlanabilir.


myQueue.hpp:

template <class T> 
class QueueA {
    int size;
    ...
public:
    template <class T> T dequeue() {
       // implementation here
    }

    bool isEmpty();

    ...
}    

myQueue.cpp:

// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
    return this->size == 0;
}


main()
{
    QueueA<char> Q;

    ...
}

2
Gerçek adam için ??? Bu durumda cevabınız doğru olanı olarak kontrol edilmelidir.Neden .cpp'de şablon olmayan üye yöntemleri tanımlayabiliyorsanız, herkes neden tüm bu voodo şeylerine ihtiyaç duyar?
Michael IV

En azından MSVC 2019'da, şablon sınıfının bir üye işlevi için çözülmemiş harici sembol elde etmek.
Michael IV

Test edilecek MSVC 2019'um yok. Buna C ++ standardı izin verir. Şimdi, MSVC her zaman kurallara uymadığı için ünlüdür. Henüz yapmadıysanız, Proje Ayarları -> C / C ++ -> Dil -> Uyumluluk Modu -> Evet (izin verilen) seçeneklerini deneyin.
Nikos

1
Bu tam örnek işe yarıyor ancak daha sonra isEmptybaşka bir çeviri biriminden arama yapamazsınız myQueue.cpp...
MM

7

Endişe, .hpp modüllerinin bir parçası olarak .h derlenerek üretilen ekstra derleme süresi ve ikili boyut şişmesi ise, birçok durumda yapabileceğiniz şablon şablonu arabirimin türe bağlı olmayan bölümleri ve bu temel sınıfın .cpp dosyasında uygulanması olabilir.


2
Bu yanıt daha da değiştirilmelidir. Aynı yaklaşımınızı " bağımsız olarak " keşfettim ve özel olarak zaten kullanmış birini arıyorum, çünkü resmi bir kalıp olup olmadığını ve bir adı olup olmadığını merak ediyorum . Benim yaklaşımım, tipe bağlı parçaları içine ve geri kalanını koyarak, class XBasea uygulamak istediğim her yerde uygulamaktır . template class XXXBase
Fabio A.

6

Bu tamamen doğru çünkü derleyici tahsis için ne tür olduğunu bilmek zorunda. Bu nedenle, şablon dosyaları, işlevler, numaralandırmalar, vb., Genel olarak veya bir kütüphanenin bir parçası (statik veya dinamik) yapılacaksa başlık dosyasında da uygulanmalıdır, çünkü başlık dosyaları c / cpp dosyalarının aksine derlenmez. vardır. Derleyici türün ne olduğunu bilmiyorsa derleyemez. .Net'te bunu yapabilir, çünkü tüm nesneler Object sınıfından türemiştir. Bu .Net değil.


5
"başlık dosyaları derlenmemiştir" - bu onu tanımlamanın gerçekten garip bir yoludur. Başlık dosyaları, tıpkı bir "c / cpp" dosyası gibi bir çeviri biriminin parçası olabilir.
Flekso

2
Aslında, neredeyse gerçeğin tam tersi, yani başlık dosyaları birçok kez çok sık derlenirken, kaynak dosya genellikle bir kez derlenir.
xaxxon

6

Derleme adımı sırasında bir şablon kullandığınızda, derleyici her şablon örneği için kod oluşturur. Derleme ve bağlama işleminde .cpp dosyaları, main.cpp'nizde bulunan .h dosyalarının uygulama YET'i olmadığından, içinde referanslar veya tanımlanmamış semboller içeren saf nesneye veya makine koduna dönüştürülür. Bunlar, şablonunuz için bir uygulama tanımlayan başka bir nesne dosyasıyla bağlanmaya hazırdır ve bu nedenle tam bir a.out yürütülebilir dosyasına sahip olursunuz.

Ancak, tanımladığınız her şablon örneği için kod oluşturmak amacıyla şablonların derleme adımında işlenmesi gerektiğinden, üstbilgi dosyasından ayrı bir şablon derlemek işe yaramaz, çünkü bu nedenle her zaman el ele gider. her şablon örneğinin, kelimenin tam anlamıyla yepyeni bir sınıf olduğunu. Normal bir sınıfta .h ve .cpp'yi ayırabilirsiniz, çünkü .h bu sınıfın bir taslağıdır ve .cpp ham uygulamadır, böylece tüm uygulama dosyaları düzenli olarak derlenebilir ve bağlanabilir, ancak şablonlar kullanmak .h, sınıf, nesnenin bir şablonun nasıl görünmesi gerektiği biçimine bakmamalıdır .cpp dosyası bir sınıfın ham normal uygulaması değil, sadece bir sınıf için bir taslaktır, bu nedenle .h şablon dosyasının herhangi bir uygulaması '

Bu nedenle şablonlar hiçbir zaman ayrı ayrı derlenmez ve yalnızca başka bir kaynak dosyada somut bir somut örneğinizin olduğu her yerde derlenir. Ancak, somut somutlaştırmanın şablon dosyasının uygulanmasını bilmesi gerekir, çünkütypename T.hpp dosyasında somut bir tür kullanmak işi yapmayacaktır çünkü bağlantı için .cpp orada ne var, daha sonra bulamıyorum çünkü şablonları soyut ve derlenemez hatırlıyorum, bu yüzden zorla ne derlemek ve bağlantı bilmek biliyorum şimdi ve şimdi vermek için, ve şimdi uygulama kaynak dosyası içine bağlı alır. Temel olarak, bir şablon oluşturduğum anda tamamen yeni bir sınıf oluşturmam gerekiyor ve derleyiciye haber vermedikçe, sağladığım türü kullanırken bu sınıfın nasıl görünmesi gerektiğini bilmiyorsam bunu yapamam şablon uygulaması, böylece derleyici Tbenim türüm ile değiştirebilir ve derlenip bağlanmaya hazır somut bir sınıf oluşturabilir.

Özetle, şablonlar sınıfların nasıl görünmesi gerektiğine dair taslaklardır, sınıflar bir nesnenin nasıl görünmesi gerektiğine dair taslaklardır. Derleyicileri yalnızca somut türleri derlediğinden, başka bir deyişle, en azından C ++ 'daki şablonlar saf dil soyutlaması olduğundan, somut örneklemelerinden ayrı şablonları derleyemiyorum. Şablonları konuşmak için soyutlamalıyız ve bunları ele almaları için somut bir tür vererek şablon soyutlamasının normal bir sınıf dosyasına dönüşebilmesini ve normal olarak derlenebilmesini sağlarız. Şablon .h dosyasını ve şablon .cpp dosyasını ayırmak anlamsızdır. Bu .cpp ve .h'nin ayrılması yalnızca .cpp'in ayrı ayrı derlenebildiği ve şablonlarla ayrı ayrı bağlanabildiği bir yerdir, çünkü bunları ayrı olarak derleyemediğimiz için şablonlar bir soyutlamadır,

Anlam typename Tget derleme adımı sırasında bağlantı adım değil değiştirilir, bu yüzden bir şablonu Tderleyici için tamamen anlamsız somut bir değer türü olarak değiştirmeden derlemeye çalışırsam ve sonuç olarak nesne kodu oluşturulamaz çünkü ne Tolduğunu bilmek .

Template.cpp dosyasını kaydedecek ve diğer kaynaklarda bulduğunda türleri kapatacak bir tür işlevsellik oluşturmak teknik olarak mümkündür, standardın exportşablonları ayrı bir yere koymanıza izin verecek bir anahtar kelimeye sahip olduğunu düşünüyorum cpp dosyası ama pek çok derleyici aslında bunu uygulamak değil.

Sadece bir yan not, bir şablon sınıfı için uzmanlık yaparken, üstbilgiyi uygulamadan ayırabilirsiniz, çünkü tanım gereği bir uzmanlaşma, ayrı ayrı derlenip bağlanabilen somut bir tür için uzmanlaştığım anlamına gelir.


4

Ayrı bir uygulamaya sahip olmanın bir yolu aşağıdaki gibidir.

//inner_foo.h

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


//foo.tpp
#include "inner_foo.h"
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}


//foo.h
#include <foo.tpp>

//main.cpp
#include <foo.h>

inner_foo için ileri bildirimler var. foo.tpp uygulaması vardır ve inner_foo.h; ve foo.h, foo.tpp'yi dahil etmek için yalnızca bir satıra sahip olacaktır.

Derleme zamanında, foo.h içeriği foo.tpp dosyasına kopyalanır ve daha sonra tüm dosya foo.h dosyasına kopyalanır ve ardından derlenir. Bu şekilde, herhangi bir sınırlama yoktur ve adlandırma, fazladan bir dosya karşılığında tutarlıdır.

Bunu yapmak için çünkü * .tpp sınıf ileri bildirimlerini görmüyor kod için statik çözümleyicileri kırmak. Herhangi bir IDE'de kod yazarken veya YouCompleteMe veya diğerlerini kullanırken bu can sıkıcı bir durumdur.


2
s / inner_foo / foo / g ve foo.h öğesinin sonuna foo.tpp ekleyin. Bir tane daha az dosya.

1

Şablon örneklemeleri için "cfront" ve "borland" modeli arasındaki dengeleri tartışan bu gcc sayfasına bakmanızı öneririm.

https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html

"Borland" modeli, yazarın önerdiği şeye karşılık gelir, tam şablon tanımını sağlar ve birçok kez derlenmesini sağlar.

Manuel ve otomatik şablon örneklemenin kullanımına ilişkin açık öneriler içerir. Örneğin, "-repo" seçeneği, somutlaştırılması gereken şablonları toplamak için kullanılabilir. Veya başka bir seçenek, el ile şablon başlatmayı zorlamak için "-fno-implicit-templates" kullanarak otomatik şablon örneklerini devre dışı bırakmaktır.

Deneyimlerime göre, her derleme birimi (şablon kitaplığı kullanarak) için başlatılan C ++ Standart Kitaplığı ve Boost şablonlarına güveniyorum. Büyük şablon sınıflarım için, ihtiyaç duyduğum türler için bir kez manuel şablon örneklemesi yapıyorum.

Bu benim yaklaşımım çünkü diğer programlarda kullanmak için bir şablon kütüphanesi değil, çalışan bir program sağlıyorum. Kitabın yazarı Josuttis, şablon kütüphanelerinde çok çalışıyor.

Hız konusunda gerçekten endişeliysem, Önceden Derlenmiş Üstbilgileri kullanarak keşfe çıkacağımı sanırım https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html

bu da birçok derleyicide destek kazanıyor. Ancak, önceden derlenmiş başlıkların şablon başlık dosyalarıyla zor olacağını düşünüyorum.


-2

Üstbilgi dosyalarına hem bildirimleri hem de tanımları yazmanın iyi bir fikir olmasının başka bir nedeni de okunabilirliktir. Utility.h dosyasında böyle bir şablon işlevi olduğunu varsayalım:

template <class T>
T min(T const& one, T const& theOther);

Ve Utility.cpp dosyasında:

#include "Utility.h"
template <class T>
T min(T const& one, T const& other)
{
    return one < other ? one : other;
}

Bu, buradaki her T sınıfının operatörden daha azını (<) uygulamasını gerektirir. "<" Karakterini uygulamayan iki sınıf örneğini karşılaştırdığınızda derleyici hatası verir.

Bu nedenle, şablon bildirimini ve tanımını ayırırsanız, derleyici size bunu söyleyecek olsa da, bu API'yi kendi sınıflarınızda kullanmak için bu şablonun giriş ve çıkışlarını görmek için yalnızca başlık dosyasını okuyamazsınız. hangi operatörün geçersiz kılınması gerektiği konusunda.


-7

Şablon sınıfınızı aslında bir .cpp dosyası yerine bir .template dosyası içinde tanımlayabilirsiniz. Bunu sadece bir başlık dosyasında tanımlayabileceğinizi söyleyen kişi yanlıştır. Bu, c ++ 98'e kadar çalışan bir şeydir.

Derleyicinizin .template dosyanıza zekayı korumak için c ++ dosyası gibi davranmasını unutmayın.

Dinamik dizi sınıfı için bunun bir örneği.

#ifndef dynarray_h
#define dynarray_h

#include <iostream>

template <class T>
class DynArray{
    int capacity_;
    int size_;
    T* data;
public:
    explicit DynArray(int size = 0, int capacity=2);
    DynArray(const DynArray& d1);
    ~DynArray();
    T& operator[]( const int index);
    void operator=(const DynArray<T>& d1);
    int size();

    int capacity();
    void clear();

    void push_back(int n);

    void pop_back();
    T& at(const int n);
    T& back();
    T& front();
};

#include "dynarray.template" // this is how you get the header file

#endif

Şimdi içinizde .template dosyası işlevlerinizi normalde nasıl yapacağınızı tanımlarsınız.

template <class T>
DynArray<T>::DynArray(int size, int capacity){
    if (capacity >= size){
        this->size_ = size;
        this->capacity_ = capacity;
        data = new T[capacity];
    }
    //    for (int i = 0; i < size; ++i) {
    //        data[i] = 0;
    //    }
}

template <class T>
DynArray<T>::DynArray(const DynArray& d1){
    //clear();
    //delete [] data;
    std::cout << "copy" << std::endl;
    this->size_ = d1.size_;
    this->capacity_ = d1.capacity_;
    data = new T[capacity()];
    for(int i = 0; i < size(); ++i){
        data[i] = d1.data[i];
    }
}

template <class T>
DynArray<T>::~DynArray(){
    delete [] data;
}

template <class T>
T& DynArray<T>::operator[]( const int index){
    return at(index);
}

template <class T>
void DynArray<T>::operator=(const DynArray<T>& d1){
    if (this->size() > 0) {
        clear();
    }
    std::cout << "assign" << std::endl;
    this->size_ = d1.size_;
    this->capacity_ = d1.capacity_;
    data = new T[capacity()];
    for(int i = 0; i < size(); ++i){
        data[i] = d1.data[i];
    }

    //delete [] d1.data;
}

template <class T>
int DynArray<T>::size(){
    return size_;
}

template <class T>
int DynArray<T>::capacity(){
    return capacity_;
}

template <class T>
void DynArray<T>::clear(){
    for( int i = 0; i < size(); ++i){
        data[i] = 0;
    }
    size_ = 0;
    capacity_ = 2;
}

template <class T>
void DynArray<T>::push_back(int n){
    if (size() >= capacity()) {
        std::cout << "grow" << std::endl;
        //redo the array
        T* copy = new T[capacity_ + 40];
        for (int i = 0; i < size(); ++i) {
            copy[i] = data[i];
        }

        delete [] data;
        data = new T[ capacity_ * 2];
        for (int i = 0; i < capacity() * 2; ++i) {
            data[i] = copy[i];
        }
        delete [] copy;
        capacity_ *= 2;
    }
    data[size()] = n;
    ++size_;
}

template <class T>
void DynArray<T>::pop_back(){
    data[size()-1] = 0;
    --size_;
}

template <class T>
T& DynArray<T>::at(const int n){
    if (n >= size()) {
        throw std::runtime_error("invalid index");
    }
    return data[n];
}

template <class T>
T& DynArray<T>::back(){
    if (size() == 0) {
        throw std::runtime_error("vector is empty");
    }
    return data[size()-1];
}

template <class T>
T& DynArray<T>::front(){
    if (size() == 0) {
        throw std::runtime_error("vector is empty");
    }
    return data[0];
    }

2
Çoğu kişi, başlık dosyasını tanımları kaynak dosyalara yayan bir şey olarak tanımlar. Yani ".template" dosya uzantısını kullanmaya karar vermiş olabilirsiniz, ancak bir başlık dosyası yazdınız.
Tommy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.