harici şablon kullanma (C ++ 11)


116

Şekil 1: işlev şablonları

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;
}

Kullanmanın doğru yolu bu mu extern templateyoksa bu anahtar kelimeyi yalnızca Şekil 2'deki gibi sınıf şablonları için mi kullanıyorum?

Şekil 2: sınıf şablonları

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;
}

Bunların hepsini tek bir başlık dosyasına koymanın iyi olduğunu biliyorum, ancak birden fazla dosyada aynı parametrelere sahip şablonları başlatırsak, birden fazla aynı tanım elde ederiz ve derleyici hataları önlemek için hepsini (biri hariç) kaldıracaktır. Nasıl kullanırım extern template? Onu sadece sınıflar için kullanabilir miyiz, yoksa fonksiyonlar için de kullanabilir miyiz?

Ayrıca, Şekil 1 ve Şekil 2, şablonların tek bir başlık dosyasında olduğu bir çözüme genişletilebilir. Bu durumda, extern templatebirden fazla aynı anlıktan kaçınmak için anahtar kelimeyi kullanmamız gerekir . Bu sadece sınıflar veya işlevler için mi?


3
Bu, harici şablonların doğru kullanımı değil ... bu bile derlenmiyor
Dani

(Bir) soruyu daha net bir şekilde ifade etmek için biraz zaman ayırabilir misiniz? Kodu ne için gönderiyorsunuz? Bununla ilgili bir soru görmüyorum. Ayrıca extern template class foo<int>();bir hata gibi görünüyor.
sehe

@Dani> görsel stüdyom 2010'da şu uyarı mesajı dışında gayet iyi derliyor: Uyarı 1 uyarısı C4231: standart olmayan uzantı kullanıldı: şablonun açık bir şekilde başlatılmasından önce 'extern'
codekiddy

2
@sehe soru çok basit: extern şablon anahtar kelimesi nasıl ve ne zaman kullanılır? (extern şablonu C ++ 0x new future btw'dir) "Ayrıca, extern şablon sınıfı foo <int> (); bir hata gibi görünüyor" dediniz. hayır öyle değil, yeni C ++ kitabım var ve bu kitabımdan bir örnek.
codekiddy

1
@codekiddy: o zaman görsel stüdyo gerçekten aptal .. ikincisinde prototip uygulama ile uyuşmuyor ve hatta ()harici satırın yakınında 'beklenen niteliksiz-id' yazmasını düzeltsem bile . hem kitabınız hem de görsel stüdyonuz yanlış, g ++ veya clang gibi daha standart uyumlu derleyici kullanmayı deneyin ve sorunu göreceksiniz.
Dani

Yanıtlar:


181

Yalnızca kullanmalısınız extern templateiçin derleyici zorlamak için değil zaman bir şablon örneğini biliyor başka bir yere örneği olacağını. Derleme süresini ve nesne dosya boyutunu azaltmak için kullanılır.

Örneğin:

// 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>();
}

Bu, aşağıdaki nesne dosyalarıyla sonuçlanacaktır:

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

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

Her iki dosya birbirine bağlıysa, biri void ReallyBigFunction<int>()atılacak ve bu da derleme süresinin ve nesne dosya boyutunun boşa harcanmasına neden olacaktır.

Derleme zamanını ve nesne dosya boyutunu boşa harcamamak için extern, derleyicinin bir şablon işlevi derlememesini sağlayan bir anahtar sözcük vardır. Bunu, ancak ve ancak başka bir yerde aynı ikili dosyada kullanıldığını biliyorsanız kullanmalısınız .

Şuna değiştiriliyor source2.cpp:

// source2.cpp

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

Aşağıdaki nesne dosyalarıyla sonuçlanacaktır:

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

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

Bunların her ikisi de birbirine bağlandığında, ikinci nesne dosyası yalnızca birinci nesne dosyasındaki sembolü kullanacaktır. Atmaya ve derleme zamanını ve nesne dosya boyutunu boşa harcamaya gerek yok.

Bu, yalnızca bir proje içinde kullanılmalıdır, örneğin bir şablonu vector<int>birden çok kez kullandığınızda olduğu gibi , externbir kaynak dosya hariç tümünde kullanmalısınız .

Bu aynı zamanda sınıflar için de geçerlidir ve bir ve hatta şablon üye işlevleri olarak işlev görür.


2
@codekiddy: Visual Studio'nun bununla ne anlama geldiğine dair hiçbir fikrim yok. Çoğu c ++ 11 kodunun doğru çalışmasını istiyorsanız, gerçekten daha uyumlu bir derleyici kullanmalısınız.
Dani

4
@Dani: Şu ana kadar okuduğum harici şablonların en iyi açıklaması!
Pietro

90
"aynı ikili dosyada başka bir yerde kullanıldığını biliyorsanız." Bu ne yeterli ne de gerekli. Kodunuz "biçimsiz, teşhis gerekmez". Başka bir TU'nun örtük bir örneğine güvenmenize izin verilmez (derleyicinin, bir satır içi işlev gibi onu uzaklaştırmasına izin verilir). Başka bir OG'de açık bir örnekleme sağlanmalıdır.
Johannes Schaub -

32
Bu cevabın muhtemelen yanlış olduğunu ve beni ısırdığını belirtmek isterim. Neyse ki Johannes'in yorumunda bir dizi artı oy vardı ve bu sefer buna daha fazla dikkat ettim. Yalnızca bu sorudaki seçmenlerin büyük çoğunluğunun bu tür şablonları birden çok derleme biriminde uygulamadığını varsayabilirim (bugün yaptığım gibi) ... En azından clang için, tek kesin yol bu şablon tanımlarını koymaktır başlığın! Dikkatli olun!
Steven Lu

6
@ JohannesSchaub-litb, biraz daha detaylandırabilir misiniz veya daha iyi bir cevap verebilir misiniz? İtirazlarınızı tam olarak anladığımdan emin değilim.
andreee

48

Wikipedia en iyi açıklamaya sahip

C ++ 03'te, bir çeviri biriminde tam olarak belirtilen bir şablonla karşılaşıldığında derleyicinin bir şablonu başlatması gerekir. Şablon, birçok çeviri biriminde aynı türlerle örneklenirse, bu, derleme sürelerini önemli ölçüde artırabilir. C ++ 03'te bunu önlemenin bir yolu yoktur, bu nedenle C ++ 11, extern veri bildirimlerine benzer şekilde extern şablon bildirimleri sundu.

C ++ 03, derleyiciyi bir şablonu başlatmaya zorlamak için bu sözdizimine sahiptir:

  template class std::vector<MyClass>;

C ++ 11 artık şu sözdizimini sağlar:

  extern template class std::vector<MyClass>;

bu, derleyiciye şablonu bu çeviri biriminde başlatmamasını söyler.

Uyarı: nonstandard extension used...

Microsoft VC ++ , birkaç yıldır bu özelliğin standart olmayan bir sürümüne sahipti (C ++ 03'te). Derleyici, farklı derleyicilerde de derlenmesi gereken kodla ilgili taşınabilirlik sorunlarını önlemek için bu konuda uyarır.

Kabaca aynı şekilde çalıştığını görmek için bağlantılı sayfadaki örneğe bakın. Diğer standart olmayan derleyici uzantılarını aynı anda kullanırken, mesajın MSVC'nin gelecekteki sürümlerinde kaybolmasını bekleyebilirsiniz .


tnx cevabınız için sehe, bu nedenle bu "extern şablon" geleceğinin VS 2010 için tam olarak çalıştığı ve uyarıyı görmezden gelebilir miyiz? (örneğin mesajı yok saymak için pragma kullanarak) ve bu şablonun VSC ++ 'da zamanında olduğundan daha fazla somutlaştırılmamasını sağlayın. derleyici. Teşekkürler.
codekiddy

4
"... derleyiciye şablonu bu çeviri biriminde başlatmamasını söyler ." Bunun doğru olduğunu sanmıyorum. Sınıf tanımında tanımlanan herhangi bir yöntem satır içi olarak sayılır, bu nedenle STL uygulaması için satır içi yöntemler kullanıyorsa std::vector(hepsinin kullanacağından oldukça emin olabilirsiniz) externhiçbir etkisi olmaz.
Andreas Haferburg

Evet, bu cevap yanıltıcıdır. MSFT belgesi: "Uzmanlaşmadaki extern anahtar sözcüğü yalnızca sınıf gövdesi dışında tanımlanan üye işlevler için geçerlidir. Sınıf bildirimi içinde tanımlanan işlevler satır içi işlevler olarak kabul edilir ve her zaman başlatılır." VS'deki tüm STL sınıfları (en son kontrol edilen 2017 idi) maalesef yalnızca satır içi yöntemlere sahip.
0kcats

Bu, nerede ortaya çıktıklarına bakılmaksızın tüm satır içi bildirimler için geçerlidir, her zaman @ 0kcats
sehe

@sehe std :: vector örneğiyle Wiki'ye gönderme ve aynı yanıtta MSVC'ye gönderme, MSVC'de extern std :: vector kullanmanın bir faydası olabileceğine inandırır, ancak şimdiye kadar böyle bir şey yoktur. Bunun standardın gereği olup olmadığından emin değilim, belki diğer derleyiciler aynı sorunu yaşayabilir.
0kcats

7

extern template yalnızca şablon beyanı tamamlandığında gereklidir

Bu, başka cevaplarda ima edildi, ancak buna yeterince vurgu yapıldığını sanmıyorum.

Bunun anlamı, OP örneklerinde, extern templatebaşlıklardaki şablon tanımlarının eksik olması nedeniyle hiçbir etkisinin olmadığıdır:

  • void f();: sadece beyan, gövde yok
  • class foo: yöntemi bildirir f()ancak tanımı yoktur

Bu nedenle extern template, bu belirli durumda tanımı kaldırmanızı tavsiye ederim : yalnızca sınıflar tamamen tanımlanmışsa bunları eklemeniz gerekir.

Örneğin:

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;
}

sembolleri şununla derleyin ve görüntüleyin 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

çıktı:

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

ve sonra man nmbunun Utanımsız anlamına geldiğini görüyoruz , bu nedenle tanım yalnızca TemplCppistendiği gibi kaldı.

Bütün bunlar, tam başlık bildirimlerinin değiş tokuşuna indirgeniyor:

  • upsides:
    • harici kodun şablonumuzu yeni türlerle kullanmasına izin verir
    • nesne bloat'ında sorun yoksa, açık örnekler eklememe seçeneğimiz var
  • downsides:
    • Bu sınıfı geliştirirken, üstbilgi uygulaması değişiklikleri akıllı derleme sistemlerinin, birçok dosya olabilecek tüm dosyaları yeniden oluşturmasına yol açacaktır.
    • nesne dosyası şişkinliğini önlemek istiyorsak, yalnızca açık örnekler (tamamlanmamış başlık bildirimleriyle aynı) extern templateyapmakla kalmamalı, aynı zamanda programcıların muhtemelen yapmayı unutacağı her bir dahil ediciye eklememiz gerekir .

Bunların diğer örnekleri şu adreste gösterilmektedir: Açık şablon somutlaştırma - ne zaman kullanılır?

Derleme süresi büyük projelerde çok kritik olduğundan, harici tarafların kodunuzu kendi karmaşık özel sınıflarıyla kesinlikle yeniden kullanmaları gerekmediği sürece, eksik şablon bildirimlerini şiddetle tavsiye ederim.

Ve bu durumda, derleme zamanı probleminden kaçınmak için önce polimorfizmi kullanmayı denerdim ve sadece belirgin performans kazanımları elde edilebiliyorsa şablonları kullanırdım.

Ubuntu 18.04'te test edilmiştir.


4

Şablonlarla ilgili bilinen sorun, sınıf şablonu uzmanlığını çağıran her modülde sınıf tanımının oluşturulmasının bir sonucu olan kod şişkinliğidir. Bunu önlemek için, C ++ 0x ile başlayarak , sınıf şablonu uzmanlığının önünde extern anahtar kelimesi kullanılabilir.

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

Şablon sınıfının açık anında oluşması yalnızca tek bir çeviri biriminde gerçekleşmelidir, tercihen şablon tanımlı olanı (Sınıfım.cpp)

template class CMyClass<int>;
template class CMyClass<float>;

0

Daha önce fonksiyonlar için extern kullandıysanız, şablonlar için tamamen aynı felsefe izlenir. değilse, basit işlevler için dışarıdan gitmek yardımcı olabilir. Ayrıca, extern (ler) i başlık dosyasına koymak ve ihtiyacınız olduğunda başlığı eklemek isteyebilirsiniz.

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.