C ++ şablon işlev tanımlarını bir .CPP dosyasında depolama


526

Başlıkta satır içi yerine bir CPP dosyasında depolamak tercih ederim bazı şablon kodu var. Hangi şablon türlerinin kullanılacağını bildiğiniz sürece bunun yapılabileceğini biliyorum. Örneğin:

.h dosyası

class foo
{
public:
    template <typename T>
    void do(const T& t);
};

.cpp dosyası

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&);

Son iki satırı not edin - foo :: do şablon işlevi yalnızca ints ve std :: dizeleri ile kullanılır, bu nedenle bu tanımlar uygulamanın bağlanacağı anlamına gelir.

Benim sorum - bu kötü bir kesmek mi yoksa bu diğer derleyiciler / bağlayıcılar ile çalışır mı? Şu anda sadece VS2008 ile bu kodu kullanıyorum ama diğer ortamlara bağlantı noktası isteyen olacak.


22
Bunun mümkün olduğuna dair hiçbir fikrim yoktu - ilginç bir numara! Bunu bilmek önemli bazı son görevlere yardımcı olurdu - şerefe!
xan

69
Beni durduran şey dotanımlayıcı olarak kullanılması: p
Quentin

ben gcc ile benzer bir şey yaptık, ama hala araştırma
Nick

16
Bu bir "hack" değil, ileriye doğru bir düşüş. Bu, dilin standardında bir yere sahiptir; yani evet, her standart uyumlu derleyicide izin verilir.
Ahmet Ipkin

1
Düzinelerce yönteminiz varsa ne olur? Sadece template class foo<int>;template class foo<std::string>;.cpp dosyasının sonunda yapabilir misiniz ?
Cahil

Yanıtlar:


231

Açıkladığınız sorun, başlıkta şablon tanımlanarak veya yukarıda tarif ettiğiniz yaklaşımla çözülebilir.

C ++ FAQ Lite'tan aşağıdaki noktaları okumanızı tavsiye ederim :

Bu (ve diğer) şablon sorunları hakkında birçok ayrıntıya girerler.


39
Sadece cevabı tamamlamak için, referans verilen bağlantı soruyu olumlu cevaplar, yani Rob'un önerdiği şeyi yapmak ve kodun taşınabilir olması mümkündür.
ivotron

161
İlgili parçaları cevabın kendisinde yayınlayabilir misiniz? Neden böyle bir referans SO üzerinde bile izin verilir. O zamandan beri yoğun bir şekilde değiştiği için bu bağlantıda ne arayacağımla ilgili hiçbir fikrim yok.
Ident

124

Bu sayfadaki açık şablon uzmanlığı (veya en azından VS2008'de) için doğru sözdiziminin (ben yaptığım gibi) ne olduğunu merak eden diğerleri için, bu ...

.H dosyanızda ...

template<typename T>
class foo
{
public:
    void bar(const T &t);
};

Ve .cpp dosyanızda

template <class T>
void foo<T>::bar(const T &t)
{ }

// Explicit template instantiation
template class foo<int>;

15
Şunu mu demek istediniz: "açık CLASS şablon uzmanlaşması için". Bu durumda, şablon sınıfın sahip olduğu her işlevi kapsayacak mı?
Arthur

@Arthur gibi görünmüyor, bazı şablon yöntemleri başlıkta kalmak ve diğer yöntemlerin çoğu cpp, iyi çalışıyor. Çok güzel bir çözüm.
user1633272

Sorucunun durumunda, sınıf şablonu değil, işlev şablonu vardır.
user253751

23

Bu kod iyi biçimlendirilmiş. Yalnızca şablon tanımının örnekleme noktasında görünür olmasına dikkat etmeniz gerekir. Standardı teklif etmek için, § 14.7.2.4:

Dışa aktarılmayan bir işlev şablonunun, dışa aktarılmayan bir üye işlev şablonunun veya dışa aktarılmayan bir üye işlevinin veya bir sınıf şablonunun statik veri üyesinin tanımı, açıkça somutlaştırıldığı her çeviri biriminde bulunacaktır.


2
Dışa aktarılmayan ne anlama geliyor?
Dan Nissenbaum

1
@Dan Sadece derleme birimi içinde görünür, dışında değil. Birden fazla derleme birimini birbirine bağlarsanız, dışa aktarılan semboller bunlar arasında kullanılabilir (ve şablonlar söz konusu olduğunda tek bir veya en azından tutarlı tanımlara sahip olmalıdır, aksi takdirde UB ile karşılaşırsınız).
Konrad Rudolph

Teşekkürler. Tüm fonksiyonların (varsayılan olarak) derleme biriminin dışında göründüğünü düşündüm. Eğer iki derleme birimim varsa a.cpp(işlevi tanımlayan a() {}) ve b.cpp(işlevi tanımlayan b() { a() }), bu başarılı bir şekilde bağlanır. Haklıysam, yukarıdaki alıntı tipik dava için geçerli değil gibi görünüyor ... bir yerde yanlış mı gidiyorum?
Dan Nissenbaum

@Dan Önemsiz karşı örnek: inlinefonksiyonlar
Konrad Rudolph

1
@Dan Fonksiyon şablonları dolaylı olarak inline. Bunun nedeni, standartlaştırılmış bir C ++ ABI olmadan, bunun aksi takdirde elde edeceği etkiyi tanımlamanın zor / imkansız olmasıdır.
Konrad Rudolph

15

Bu, şablonların desteklendiği her yerde iyi çalışmalıdır. Açık şablon örneği C ++ standardının bir parçasıdır.


13

Örneğin doğru ama taşınabilir değil. Kullanılabilecek biraz daha temiz bir sözdizimi de vardır (@ namespace-sid tarafından belirtildiği gibi).

Şablonlu sınıfın paylaşılacak bazı kitaplığın parçası olduğunu varsayalım. Geçici sınıfın diğer sürümleri derlenmeli mi? Kütüphane sorumlusunun sınıfın olası tüm geçici kullanımlarını tahmin etmesi gerekiyor mu?

Alternatif bir yaklaşım, sahip olduğunuz şeyde küçük bir değişikliktir: şablon uygulama / örnekleme dosyası olan üçüncü bir dosya ekleyin.

foo.h dosyası

// Standard header file guards omitted

template <typename T>
class foo
{
public:
    void bar(const T& t);
};

foo.cpp dosyası

// Always include your headers
#include "foo.h"

template <typename T>
void foo::bar(const T& t)
{
    // Do something with t
}

foo-impl.cpp dosyası

// Yes, we include the .cpp file
#include "foo.cpp"
template class foo<int>;

Bir ihtar derlemek için derleyici söylemek gerekir ki foo-impl.cppyerine foo.cppikinci şey yapmaz derleme olarak.

Tabii ki, üçüncü dosyada birden fazla uygulamanız olabilir veya kullanmak istediğiniz her tür için birden çok uygulama dosyanız olabilir.

Bu, tempolu sınıfı diğer kullanımlar için paylaşırken çok daha fazla esneklik sağlar.

Bu kurulum, her çeviri biriminde aynı başlık dosyasını yeniden derlemediğiniz için yeniden kullanılan sınıflar için derleme sürelerini de azaltır.


bu seni ne satın alıyor? Yeni bir uzmanlık eklemek için hala foo-impl.cpp dosyasını düzenlemeniz gerekir.
MK.

foo.cppSürümlerin gerçekte derlendiği (in foo-impl.cpp) ve bildirimlerin (in foo.h) uygulandığı uygulama ayrıntılarının (diğer tanımları ). Çoğu C ++ şablonunun tamamen başlık dosyalarında tanımlandığını sevmiyorum. Bu, c[pp]/hkullandığınız her sınıf / ad alanı / gruplama için C / C ++ çift standartlarına karşılık gelir . İnsanlar hala monolitik başlık dosyalarını kullanıyor gibi görünüyor çünkü bu alternatif yaygın olarak kullanılmıyor veya bilinmiyor.
Cameron Tacklind

1
@MK. Başka bir yerde başka örneklere ihtiyaç duyulana kadar ilk olarak kaynak dosyadaki tanımın sonunda açık şablon örneklerini koyuyordum (örneğin, şablon türü olarak bir sahte kullanarak birim testleri). Bu ayrım, dışarıdan daha fazla örnek eklememe izin veriyor. Dahası, orijinali bir h/cppeşleştirme koruyucusunda çevrelemek zorunda kalmama rağmen orijinali bir çift olarak tuttuğumda hala çalışıyor , ancak yine foo.cppde normal olarak derleyebilirim . Yine de C ++ için oldukça yeniyim ve bu karışık kullanımın herhangi bir ek uyarısı olup olmadığını bilmek isterim.
Thirdwater

3
Bunun decouple tercih olduğunu düşünüyorum foo.cppve foo-impl.cpp. Yapma #include "foo.cpp"içinde foo-impl.cppdosyanın; Bunun yerine, beyanı eklemek extern template class foo<int>;için foo.cppderlerken örneklenimin gelen derleyici önlemek için foo.cpp. Derleme sisteminin her iki .cppdosyayı da oluşturduğundan ve her iki nesne dosyasının da bağlayıcıya geçtiğinden emin olun . Bunun birçok faydası vardır: a) foo.cppsomutlaşma olmadığı açıktır ; b) foo.cpp'deki değişiklikler foo-impl.cpp dosyasının yeniden derlenmesini gerektirmez.
Shmuel Levine

3
Bu, her iki dünyanın da en iyisini alan şablon tanımları sorununa çok iyi bir yaklaşımdır - başlık uygulaması ve sık kullanılan türler için örnekleme. Bu kurulumda yapacağım tek değişiklik , sadece foo.cppiçine foo_impl.hve foo-impl.cppiçine yeniden adlandırmaktır foo.cpp. Ben de gelen örneklemeler için typedefs eklersiniz foo.cppiçin foo.haynı şekilde, using foo_int = foo<int>;. İşin püf noktası, kullanıcılara bir seçim için iki başlık arabirimi sağlamaktır. Kullanıcı, önceden tanımlanmış bir örneğe foo.hihtiyaç duyduğunda, içerdiği bir şeyin dışında olması gerektiğinde foo_impl.h.
Wormer

5

Bu kesinlikle kötü bir hack değil, ancak verilen şablonla kullanmak istediğiniz her sınıf / tip için bunu yapmak zorunda kalacağınızın (açık şablon uzmanlığı) farkında olun. Şablon örneğini isteyen birçok türde ise .cpp dosyanızda çok sayıda satır olabilir. Bu sorunu gidermek için, kullandığınız her projede bir TemplateClassInst.cpp dosyası olabilir, böylece hangi türlerin somutlaştırılacağını daha iyi kontrol edebilirsiniz. Açıkçası bu çözüm mükemmel olmayacak (aka gümüş mermi), çünkü ODR'yi kırabilirsin :).


ODR'yi kıracağından emin misiniz? TemplateClassInst.cpp dosyasındaki örnekleme satırları aynı kaynak dosyaya (şablon işlev tanımlarını içeren) atıfta bulunuyorsa, tüm tanımlar aynı olduğundan (yinelense bile) ODR'yi ihlal etmeyeceği garanti edilmez mi?
Dan Nissenbaum

Lütfen, ODR nedir?
2018'de kaldırılamaz

4

En son standartta, exportbu sorunu hafifletmeye yardımcı olacak bir anahtar kelime ( ) vardır, ancak Comeau dışında bildiğim hiçbir derleyicide uygulanmaz.

Bununla ilgili SSS lite'ye bakın .


2
AFAIK, ihracat öldü çünkü daha yeni ve daha yeni sorunlarla karşı karşıya kalıyorlar, her seferinde son çözümü her zaman daha kapsamlı hale getiriyorlar. Ve "dışa aktarma" anahtar kelimesi, yine de bir CPP'den "dışa aktarmanıza" olanak sağlamaz (yine de H. Sutter'ın zaten). Ben de diyorum ki: Nefesini tutmayın ...
paercebal

2
Dışa aktarmayı uygulamak için derleyici yine de tam şablon tanımını gerektirir. Elde ettiğiniz tek şey onu bir çeşit derlenmiş biçimde elde etmektir. Ama gerçekten bir anlamı yok.
Zan Lynx

2
... ve oluyor gitmiş nedeniyle asgari kazanç için aşırı komplikasyon için, standardından.
DevSolar

4

Bu, şablon işlevlerini tanımlamanın standart bir yoludur. Şablonları tanımlamak için okuduğum üç yöntem olduğunu düşünüyorum. Ya da muhtemelen 4. Her biri artıları ve eksileri ile.

  1. Sınıf tanımını tanımlar. Bunu hiç sevmiyorum çünkü sınıf tanımlarının kesinlikle referans olduğunu ve okunması kolay olduğunu düşünüyorum. Ancak, sınıftaki şablonları tanımlamak dışarıdan çok daha az zordur. Ve tüm şablon bildirimleri aynı karmaşıklık düzeyinde değildir. Bu yöntem ayrıca şablonu gerçek bir şablon haline getirir.

  2. Şablonu aynı üstbilgide, ancak sınıfın dışında tanımlayın. Çoğu zaman bu benim tercih ettiğim yol. Sınıf tanımınızı düzenli tutar, şablon gerçek bir şablon olarak kalır. Bununla birlikte, zor olabilen tam şablon adlandırma gerektirir. Ayrıca, kodunuz herkes tarafından kullanılabilir. Ancak kodunuzun satır içi olması gerekiyorsa, bu tek yoldur. Bunu, sınıf tanımlarınızın sonunda bir .INL dosyası oluşturarak da gerçekleştirebilirsiniz.

  3. Header.h ve application.CPP dosyasını main.CPP'nize ekleyin. Bence böyle yapılır. Herhangi bir ön somut örnek hazırlamanız gerekmeyecek, gerçek bir şablon gibi davranacaktır. Bununla ilgili sorunum, doğal olmaması. Normalde kaynak dosyaları eklemeyiz ve eklemeyi ummayız. Kaynak dosyayı eklediğiniz için, şablon işlevleri satır içine alınabilir.

  4. Gönderilen yol olan bu son yöntem, tıpkı 3 numaralı gibi bir kaynak dosyadaki şablonları tanımlamaktır; ancak kaynak dosyayı eklemek yerine, şablonları ihtiyacımız olanlara önceden başlatırız. Bu yöntemle ilgili hiçbir sorunum yok ve bazen kullanışlı oluyor. Büyük bir kodumuz var, satır içi olmaktan yararlanamaz, bu yüzden sadece bir CPP dosyasına koyun. Ve ortak örneklemeleri biliyorsak ve bunları önceden tanımlayabilirsek. Bu da aynı şeyi 5, 10 kez yazmamızı sağlıyor. Bu yöntem, kodumuzu özel tutma avantajına sahiptir. Ancak CPP dosyalarına küçük, düzenli olarak kullanılan işlevler koymanızı önermiyorum. Bu, kütüphanenizin performansını azaltacağından.

Şişirilmiş obj dosyasının sonuçlarının farkında değilim.


3

Evet, uzmanlaşma açık örneklemenin standart yolu budur . Belirttiğiniz gibi, bu şablonu başka türlerle başlatamazsınız.

Düzenle: yoruma göre düzeltildi.


Terminoloji konusunda seçici olmak "açık bir örnekleme" dir.
Richard Corden

2

Bir örnek verelim, bir nedenden dolayı bir şablon sınıfına sahip olmak istediğinizi varsayalım:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

Bu kodu Visual Studio ile derlerseniz - kutunun dışında çalışır. gcc, bağlayıcı hatası üretir (birden çok .cpp dosyasından aynı başlık dosyası kullanılırsa):

error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here

Uygulamayı .cpp dosyasına taşımak mümkündür, ancak daha sonra böyle bir sınıf bildirmeniz gerekir -

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test();

template <>
void DemoT<bool>::test();

// Instantiate parametrized template classes, implementation resides on .cpp side.
template class DemoT<bool>;
template class DemoT<int>;

Ve sonra .cpp şöyle görünecektir:

//test_template.cpp:
#include "test_template.h"

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

Başlık dosyasında son iki satır olmadan - gcc düzgün çalışır, ancak Visual studio bir hata oluşturur:

 error LNK2019: unresolved external symbol "public: void __cdecl DemoT<int>::test(void)" (?test@?$DemoT@H@@QEAAXXZ) referenced in function

.dll dışa aktarma yoluyla işlevi göstermek istiyorsanız şablon sınıfı sözdizimi isteğe bağlıdır, ancak bu yalnızca Windows platformu için geçerlidir - bu nedenle test_template.h şöyle görünebilir:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

#ifdef _WIN32
    #define DLL_EXPORT __declspec(dllexport) 
#else
    #define DLL_EXPORT
#endif

template <>
void DLL_EXPORT DemoT<int>::test();

template <>
void DLL_EXPORT DemoT<bool>::test();

önceki örnekten .cpp dosyasıyla.

Ancak bu, bağlayıcıya daha fazla baş ağrısı verir, bu nedenle .dll işlevini dışa aktarmazsanız önceki örneği kullanmanız önerilir.


1

Güncelleme zamanı! Bir satır içi (.inl veya muhtemelen başka bir) dosya oluşturun ve tüm tanımlarınızı bu dosyaya kopyalayın. Şablonu her işlevin ( template <typename T, ...>) üstüne eklediğinizden emin olun . Şimdi başlık dosyasını satır içi dosyaya eklemek yerine bunun tam tersini yaparsınız. Sınıfınızın ( ) bildiriminden sonra satır içi dosyayı ekleyin #include "file.inl".

Kimsenin bundan neden bahsetmediğini gerçekten bilmiyorum. Anında herhangi bir dezavantaj görmüyorum.


25
Bunun en büyük dezavantajı, temel olarak sadece şablon işlevlerini doğrudan başlıkta tanımlamakla aynı olmasıdır. Siz #include "file.inl"önişlemci, içeriğini file.inldoğrudan başlığa yapıştıracaktır . Uygulamanın başlıkta yer almasını önlemek için nedeniniz ne olursa olsun, bu çözüm bu sorunu çözmez.
Cody Gray

5
- ve teknik olarak gereksiz olarak, hat dışı templatetanımların gerektirdiği tüm ayrıntılı, zihin büken kazan plakasını yazma görevine kendinizi yüklediğiniz anlamına gelir . İnsanların bunu neden yapmak istediğini anlıyorum - şablon olmayan bildirimler / tanımlarla en fazla pariteyi elde etmek, arayüz bildirimini düzenli tutmak vb. - Ama her zaman uğraşmaya değmez. Her iki taraftaki ödünleşimleri değerlendirip en az kötü olanı seçmektir . ... namespace classbir şey oluncaya kadar : O [ lütfen bir şey olun ]
underscore_d

2
@Andrew Komitenin borularına sıkışmış gibi görünüyor, ancak bence birinin kasıtlı olmadığını söylediğini gördüm. Keşke C ++ 17'ye dönüştürmüş olsaydı. Belki gelecek on yıl.
underscore_d

@CodyGray: Teknik olarak, bu derleyici için gerçekten aynıdır ve bu nedenle derleme süresini azaltmaz. Yine de bunun, bahsettiğim bir dizi projede bahsetmeye ve uygulamaya değer olduğunu düşünüyorum. Bu yola inmek, Arayüzü tanımdan ayırmaya yardımcı olur, bu da iyi bir uygulamadır. Bu durumda ABI uyumluluğu veya benzeri ile yardımcı olmaz, ancak Arabirimi okumayı ve anlamayı kolaylaştırır.
kiloalphaindia

0

Verdiğiniz örnekte yanlış bir şey yok. Ancak işlev tanımlarını bir cpp dosyasında saklamanın etkili olmadığına inandığımı söylemeliyim. Yalnızca işlevin bildirimini ve tanımını ayırma ihtiyacını anlıyorum.

Açık sınıf örnekleme ile birlikte kullanıldığında, Boost Concept Check Library (BCCL), cpp dosyalarında şablon işlev kodu oluşturmanıza yardımcı olabilir.


8
Bu konuda verimsiz olan nedir?
Cody Gray

0

Yukarıdakilerin hiçbiri benim için çalışmadı, bu yüzden nasıl çözdüm, sınıfımda sadece 1 yöntem şablonu var ..

.h

class Model
{
    template <class T>
    void build(T* b, uint32_t number);
};

.Cpp

#include "Model.h"
template <class T>
void Model::build(T* b, uint32_t number)
{
    //implementation
}

void TemporaryFunction()
{
    Model m;
    m.build<B1>(new B1(),1);
    m.build<B2>(new B2(), 1);
    m.build<B3>(new B3(), 1);
}

Bu, bağlayıcı hatalarını önler ve TemporaryFunction'u çağırmanıza gerek yoktur

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.