przy użyciu szablonu zewnętrznego (C ++ 11)

116

Rysunek 1: szablony funkcji

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

Czy jest to właściwy sposób użycia extern template, czy też używam tego słowa kluczowego tylko dla szablonów klas, jak na rysunku 2?

Rysunek 2: szablony klas

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

Wiem, że dobrze jest umieścić to wszystko w jednym pliku nagłówkowym, ale jeśli utworzymy wystąpienia szablonów z tymi samymi parametrami w wielu plikach, otrzymamy wiele takich samych definicji i kompilator usunie je wszystkie (z wyjątkiem jednego), aby uniknąć błędów. Jak używać extern template? Czy możemy go używać tylko do klas, czy też możemy go używać do funkcji?

Również Rysunek 1 i Rysunek 2 można rozszerzyć do rozwiązania, w którym szablony znajdują się w jednym pliku nagłówkowym. W takim przypadku musimy użyć extern templatesłowa kluczowego, aby uniknąć wielu takich samych instancji. Czy dotyczy to tylko klas lub funkcji?

codekiddy
źródło
3
To wcale nie jest poprawne użycie szablonów zewnętrznych ... to nawet się nie kompiluje
Dani
Czy mógłbyś poświęcić trochę czasu na wyraźniejsze sformułowanie (jednego) pytania? Po co wysyłasz kod? Nie widzę pytania z tym związanego. extern template class foo<int>();Wydaje się też, że to pomyłka.
sehe
@Dani> kompiluje się dobrze w moim Visual Studio 2010 z wyjątkiem komunikatu ostrzegawczego: Ostrzeżenie 1, ostrzeżenie C4231: użyto niestandardowego rozszerzenia: „extern” przed jawną instancją szablonu
codekiddy
2
@sehe pytanie jest bardzo proste: jak i kiedy używać zewnętrznego słowa kluczowego szablonu? (szablon extern to C ++ 0x nowa przyszłość btw) powiedziałeś: "Poza tym klasa szablonu zewnętrznego foo <int> (); wygląda na błąd." nie, nie jest, mam nową książkę C ++ i to jest przykład z mojej książki.
codekiddy
1
@codekiddy: wtedy studio wizualne jest naprawdę głupie… w drugim prototyp nie pasuje do implementacji, a nawet jeśli naprawię, że w pobliżu ()linii zewnętrznej jest napisane „oczekiwany niekwalifikowany identyfikator” . zarówno twoja książka, jak i studio wizualne są złe, spróbuj użyć bardziej zgodnego ze standardami kompilatora, takiego jak g ++ lub clang, a zobaczysz problem.
Dani

Odpowiedzi:

181

Powinieneś używać tylko extern templatedo wymuszenia na kompilatorze, aby nie tworzył instancji szablonu, gdy wiesz , że zostanie on utworzony w innym miejscu. Służy do skrócenia czasu kompilacji i rozmiaru pliku obiektowego.

Na przykład:

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

Spowoduje to powstanie następujących plików obiektów:

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

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

Jeśli oba pliki są ze sobą połączone, jeden z nich void ReallyBigFunction<int>()zostanie odrzucony, co spowoduje marnowanie czasu kompilacji i rozmiaru pliku obiektowego.

Aby nie marnować czasu kompilacji i rozmiaru pliku obiektowego, istnieje externsłowo kluczowe, które powoduje, że kompilator nie kompiluje funkcji szablonu. Powinieneś użyć tego wtedy i tylko wtedy, gdy wiesz, że jest używany w tym samym pliku binarnym gdzie indziej.

Zmiana source2.cppna:

// source2.cpp

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

W rezultacie powstają następujące pliki obiektów:

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

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

Kiedy oba zostaną połączone ze sobą, drugi plik obiektowy użyje tylko symbolu z pierwszego pliku obiektowego. Nie ma potrzeby usuwania i marnowania czasu kompilacji i rozmiaru pliku obiektowego.

Powinno to być używane tylko w projekcie, na przykład w sytuacjach, gdy używasz szablonu vector<int>wiele razy, powinieneś używać externwe wszystkich plikach źródłowych z wyjątkiem jednego.

Dotyczy to również klas i funkcji jako jednej, a nawet funkcji składowych szablonu.

Dani
źródło
2
@codekiddy: Nie mam pojęcia, co oznacza przez to Visual Studio. Naprawdę powinieneś użyć bardziej zgodnego kompilatora, jeśli chcesz, aby większość kodu C ++ 11 działała poprawnie.
Dani,
4
@Dani: najlepsze wyjaśnienie szablonów zewnętrznych, jakie do tej pory przeczytałem!
Pietro
90
„jeśli wiesz, że jest używany w tym samym pliku binarnym gdzie indziej”. To nie jest ani wystarczające, ani wymagane. Twój kod jest „źle sformułowany, nie jest wymagana diagnostyka”. Nie możesz polegać na niejawnej instancji innej jednostki TU (kompilator może ją zoptymalizować, podobnie jak funkcja wbudowana). W innej jednostce tłumaczeniowej należy podać jawną instancję.
Johannes Schaub - litb
32
Chciałbym zaznaczyć, że ta odpowiedź jest prawdopodobnie błędna i ugryzła mnie. Na szczęście komentarz Johannesa miał kilka pozytywnych głosów i tym razem zwróciłem na niego większą uwagę. Mogę tylko założyć, że zdecydowana większość głosujących w tym pytaniu nie zaimplementowała tego typu szablonów w wielu jednostkach kompilacji (tak jak zrobiłem to dzisiaj) ... Przynajmniej dla clang, jedynym pewnym sposobem jest umieszczenie tych definicji szablonów w twój nagłówek! Być ostrzeżonym!
Steven Lu,
6
@ JohannesSchaub-litb, czy mógłbyś rozwinąć trochę więcej, czy może udzielić lepszej odpowiedzi? Nie jestem pewien, czy w pełni zrozumiałem Twoje zastrzeżenia.
andreee
48

Wikipedia ma najlepszy opis

W C ++ 03 kompilator musi utworzyć instancję szablonu zawsze, gdy w jednostce tłumaczeniowej napotkany zostanie w pełni określony szablon. Jeśli szablon ma instancję tego samego typu w wielu jednostkach tłumaczeniowych, może to znacznie wydłużyć czas kompilacji. Nie ma sposobu, aby temu zapobiec w C ++ 03, więc C ++ 11 wprowadził zewnętrzne deklaracje szablonów, analogiczne do zewnętrznych deklaracji danych.

C ++ 03 ma następującą składnię, aby zmusić kompilator do utworzenia wystąpienia szablonu:

  template class std::vector<MyClass>;

C ++ 11 udostępnia teraz następującą składnię:

  extern template class std::vector<MyClass>;

co mówi kompilatorowi, aby nie tworzył instancji szablonu w tej jednostce tłumaczeniowej.

Ostrzeżenie: nonstandard extension used...

Microsoft VC ++ już od kilku lat miał niestandardową wersję tej funkcji (w C ++ 03). Kompilator ostrzega o tym, aby zapobiec problemom z przenośnością kodu, który musiał być kompilowany również na różnych kompilatorach.

Spójrz na próbkę na połączonej stronie, aby zobaczyć, że działa mniej więcej w ten sam sposób. Możesz oczekiwać, że komunikat zniknie wraz z przyszłymi wersjami MSVC, z wyjątkiem oczywiście sytuacji, gdy w tym samym czasie używasz innych niestandardowych rozszerzeń kompilatora.

sehe
źródło
tnx za twoją odpowiedź, więc co to właściwie oznacza, że ​​„szablon zewnętrzny” w przyszłości w pełni działa dla VS 2010 i możemy po prostu zignorować ostrzeżenie? (używając pragmy na przykład do zignorowania wiadomości) i bądź na brzegu, ten szablon nie jest tworzony częściej niż na czas w VSC ++. kompilator. dzięki.
codekiddy
4
„... co mówi kompilatorowi, aby nie tworzył instancji szablonu w tej jednostce tłumaczeniowej .” Nie sądzę, żeby to była prawda. Każda metoda zdefiniowana w definicji klasy liczy się jako wbudowana, więc jeśli implementacja STL używa wbudowanych metod for std::vector(prawie na pewno wszystkie), externnie ma to żadnego efektu.
Andreas Haferburg
Tak, ta odpowiedź jest myląca. Dokument MSFT: „Słowo kluczowe extern w specjalizacji ma zastosowanie tylko do funkcji składowych zdefiniowanych poza treścią klasy. Funkcje zdefiniowane w deklaracji klasy są traktowane jako funkcje wbudowane i są zawsze tworzone.” Wszystkie klasy STL w VS (ostatnio sprawdzane to 2017) mają niestety tylko metody wbudowane.
0kcats
To dotyczy wszystkich zgłoszeń inline niezależnie od tego gdzie się pojawiają, zawsze @ 0kcats
sehe
@sehe Odniesienie do Wiki z przykładem std :: vector i odniesieniem do MSVC w tej samej odpowiedzi pozwala sądzić, że użycie extern std :: vector może przynieść korzyści w MSVC, podczas gdy jak dotąd nie ma. Nie jestem pewien, czy jest to wymóg standardu, być może inne kompilatory mają ten sam problem.
0kcats
7

extern template jest potrzebny tylko wtedy, gdy deklaracja szablonu jest kompletna

Wskazywano na to w innych odpowiedziach, ale nie sądzę, aby położono na to wystarczający nacisk.

Oznacza to, że w przykładach PO extern templatenie ma to znaczenia, ponieważ definicje szablonów w nagłówkach były niekompletne:

  • void f();: tylko deklaracja, brak treści
  • class foo: deklaruje metodę, f()ale nie ma definicji

Dlatego zalecałbym po prostu usunięcie extern templatedefinicji w tym konkretnym przypadku: wystarczy je dodać tylko wtedy, gdy klasy są całkowicie zdefiniowane.

Na przykład:

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

kompiluj i przeglądaj symbole za pomocą 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

wynik:

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

a potem, man nmjak widzimy, Uoznacza to niezdefiniowane, więc definicja pozostawała włączona tylko TemplCppzgodnie z życzeniem.

Wszystko to sprowadza się do kompromisu pełnych deklaracji nagłówka:

  • zalety:
    • umożliwia zewnętrznemu kodowi używanie naszego szablonu z nowymi typami
    • mamy opcję nie dodawania jawnych instancji, jeśli nie przeszkadza nam powiększanie obiektu
  • wady:
    • podczas opracowywania tej klasy zmiany implementacji nagłówków spowodują, że inteligentne systemy kompilacji będą przebudowywać wszystkie włączniki, które mogą składać się z wielu plików
    • jeśli chcemy uniknąć powiększania pliku obiektowego, musimy nie tylko tworzyć jawne instancje (tak samo jak w przypadku niekompletnych deklaracji nagłówków), ale także dodawać extern templatedo każdego elementu włączającego, o którym programiści prawdopodobnie zapomną

Dalsze przykłady można znaleźć pod adresem: Jawna instancja szablonu - kiedy jest używana?

Ponieważ czas kompilacji jest tak krytyczny w dużych projektach, zdecydowanie polecam niekompletne deklaracje szablonów, chyba że strony zewnętrzne absolutnie muszą ponownie użyć twojego kodu z własnymi złożonymi klasami niestandardowymi.

W takim przypadku najpierw spróbuję użyć polimorfizmu, aby uniknąć problemu z czasem kompilacji, i używać szablonów tylko wtedy, gdy można uzyskać zauważalny wzrost wydajności.

Testowane w Ubuntu 18.04.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
4

Znanym problemem związanym z szablonami jest rozdęcie kodu, które jest konsekwencją generowania definicji klasy w każdym module wywołującym specjalizację szablonu klas. Aby temu zapobiec, zaczynając od C ++ 0x, można użyć słowa kluczowego extern przed specjalizacją szablonu klasy

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

Jawna instancja klasy szablonu powinna mieć miejsce tylko w jednej jednostce tłumaczeniowej, najlepiej tej z definicją szablonu (MyClass.cpp)

template class CMyClass<int>;
template class CMyClass<float>;
damirlj
źródło
0

Jeśli wcześniej używałeś extern dla funkcji, dokładnie ta sama filozofia jest stosowana w przypadku szablonów. jeśli nie, pomocne może być skorzystanie z programu zewnętrznego w celu uzyskania prostych funkcji. Możesz także chcieć umieścić extern (y) w pliku nagłówkowym i dołączyć nagłówek, kiedy go potrzebujesz.

qqqqq
źródło