utilizzando il modello esterno (C ++ 11)


116

Figura 1: modelli di funzioni

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

È questo il modo corretto di utilizzare extern templateo devo usare questa parola chiave solo per i modelli di classe come nella Figura 2?

Figura 2: modelli di classe

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

So che è bene mettere tutto questo in un file di intestazione, ma se istanziamo modelli con gli stessi parametri in più file, avremo più definizioni uguali e il compilatore le rimuoverà tutte (tranne una) per evitare errori. Come si usa extern template? Possiamo usarlo solo per le classi o possiamo usarlo anche per le funzioni?

Inoltre, la Figura 1 e la Figura 2 possono essere espanse in una soluzione in cui i modelli sono in un unico file di intestazione. In tal caso, dobbiamo utilizzare la extern templateparola chiave per evitare più istanze stesse. È solo per classi o funzioni?


3
Questo non è affatto l'uso corretto di modelli esterni ... questo non viene nemmeno compilato
Dani

Potresti dedicare un po 'di tempo a formulare la (una) domanda in modo più chiaro? Per cosa stai pubblicando il codice? Non vedo una domanda relativa a questo. Inoltre, extern template class foo<int>();sembra un errore.
vedi

@Dani> si compila bene nel mio studio visivo 2010 eccetto il messaggio di avviso: Avviso 1 avviso C4231: estensione non standard utilizzata: 'extern' prima dell'istanza esplicita del modello
codekiddy

2
@la domanda è molto semplice: come e quando utilizzare la parola chiave del modello esterno? (il template extern è C ++ 0x new future btw) hai detto "Inoltre, extern template class foo <int> (); sembra un errore." no non lo è, ho un nuovo libro C ++ e questo è un esempio tratto dal mio libro.
codekiddy

1
@codekiddy: allora lo studio visivo è davvero stupido .. nel secondo il prototipo non corrisponde all'implementazione, e anche se aggiusto che dice "atteso unqualified-id" vicino ()sulla linea esterna. sia il tuo libro che lo studio visivo sono sbagliati, prova a usare un compilatore conforme agli standard come g ++ o clang e vedrai il problema.
Dani

Risposte:


181

Dovresti usare solo extern templateper forzare il compilatore a non istanziare un modello quando sai che verrà istanziato da qualche altra parte. Viene utilizzato per ridurre il tempo di compilazione e la dimensione del file oggetto.

Per esempio:

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

Ciò si tradurrà nei seguenti file oggetto:

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

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

Se entrambi i file sono collegati insieme, uno void ReallyBigFunction<int>()verrà scartato, con conseguente perdita di tempo di compilazione e dimensione del file oggetto.

Per non sprecare tempo di compilazione e dimensioni del file oggetto, esiste una externparola chiave che impedisce al compilatore di compilare una funzione modello. Dovresti usarlo se e solo se sai che è usato nello stesso binario da qualche altra parte.

Passaggio source2.cppa:

// source2.cpp

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

Risulterà nei seguenti file oggetto:

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

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

Quando entrambi saranno collegati insieme, il secondo file oggetto utilizzerà semplicemente il simbolo del primo file oggetto. Nessuna necessità di eliminazione e nessuna perdita di tempo di compilazione e dimensione del file oggetto.

Questo dovrebbe essere usato solo all'interno di un progetto, come quando usi un modello vector<int>più volte, dovresti usarlo externin tutti i file sorgente tranne uno.

Ciò si applica anche alle classi e alla funzione come una sola, e anche alle funzioni membro del modello.


2
@ codekiddy: non ho idea di cosa significhi Visual Studio con questo. Dovresti davvero usare un compilatore più conforme se desideri che la maggior parte del codice c ++ 11 funzioni correttamente.
Dani

4
@Dani: la migliore spiegazione dei modelli esterni che ho letto finora!
Pietro

90
"se sai che è usato nello stesso binario da qualche altra parte.". Questo non è né sufficiente né richiesto. Il tuo codice è "mal formato, nessuna diagnostica richiesta". Non è consentito fare affidamento su un'istanza implicita di un'altra TU (il compilatore può ottimizzarla, proprio come una funzione inline). È necessario fornire un'istanza esplicita in un'altra TU.
Johannes Schaub - litb

32
Vorrei sottolineare che questa risposta è probabilmente sbagliata, e ne sono stato morso. Fortunatamente il commento di Johannes ha avuto un certo numero di voti positivi e questa volta ci ho prestato maggiore attenzione. Posso solo supporre che la stragrande maggioranza degli elettori su questa domanda non abbia effettivamente implementato questi tipi di modelli in più unità di compilazione (come ho fatto oggi) ... Almeno per clang, l'unico modo sicuro è inserire queste definizioni di modelli in la tua intestazione! Stai attento!
Steven Lu

6
@ JohannesSchaub-litb, potresti elaborare un po 'di più o forse fornire una risposta migliore? Non sono sicuro di aver compreso completamente le tue obiezioni.
andreee

48

Wikipedia ha la migliore descrizione

In C ++ 03, il compilatore deve creare un'istanza di un modello ogni volta che viene incontrato un modello completamente specificato in un'unità di traduzione. Se il modello viene istanziato con gli stessi tipi in molte unità di traduzione, ciò può aumentare notevolmente i tempi di compilazione. Non c'è modo di impedirlo in C ++ 03, quindi C ++ 11 ha introdotto dichiarazioni di modelli esterni, analoghe alle dichiarazioni di dati esterni.

C ++ 03 ha questa sintassi per obbligare il compilatore a creare un'istanza di un modello:

  template class std::vector<MyClass>;

C ++ 11 ora fornisce questa sintassi:

  extern template class std::vector<MyClass>;

che dice al compilatore di non istanziare il modello in questa unità di traduzione.

L'avviso: nonstandard extension used...

Microsoft VC ++ aveva una versione non standard di questa funzionalità già da alcuni anni (in C ++ 03). Il compilatore avverte di ciò per evitare problemi di portabilità con il codice che doveva essere compilato anche su diversi compilatori.

Guarda l'esempio nella pagina collegata per vedere che funziona più o meno allo stesso modo. Puoi aspettarti che il messaggio scompaia con le versioni future di MSVC, tranne ovviamente quando si utilizzano contemporaneamente altre estensioni del compilatore non standard.


tnx per la tua risposta sehe, quindi ciò che questo significa concretamente è che il futuro "modello esterno" funziona completamente per VS 2010 e possiamo semplicemente ignorare l'avvertimento? (usando il pragma per ignorare il messaggio, ad esempio) ed essere a terra che il modello non viene istanziato più che in tempo in VSC ++. compilatore. Grazie.
codekiddy

4
"... che dice al compilatore di non istanziare il modello in questa unità di traduzione ." Non credo che questo sia vero. Qualsiasi metodo definito nella definizione della classe conta come inline, quindi se l'implementazione STL utilizza metodi inline per std::vector(quasi sicuramente tutti lo fanno), externnon ha alcun effetto.
Andreas Haferburg

Sì, questa risposta è fuorviante. Documento MSFT: "La parola chiave extern nella specializzazione si applica solo alle funzioni membro definite al di fuori del corpo della classe. Le funzioni definite all'interno della dichiarazione di classe sono considerate funzioni inline e sono sempre istanziate." Sfortunatamente, tutte le classi STL in VS (l'ultima verifica è stata 2017) hanno solo metodi inline.
0kcats

Questo vale per tutte le dichiarazioni in linea indipendentemente da dove si presentano, sempre @ 0kcats
vedi

@sehe Il riferimento a Wiki con l'esempio std :: vector e un riferimento a MSVC nella stessa risposta fa credere che potrebbe esserci qualche vantaggio nell'usare extern std :: vector in MSVC, mentre finora non c'è. Non sono sicuro che questo sia un requisito dello standard, forse altri compilatori hanno lo stesso problema.
0kcats

7

extern template è necessario solo se la dichiarazione del modello è completa

Questo è stato accennato in altre risposte, ma non credo che sia stata data abbastanza enfasi.

Ciò significa che negli esempi OP, extern templatenon ha alcun effetto perché le definizioni del modello sulle intestazioni erano incomplete:

  • void f();: solo dichiarazione, nessun corpo
  • class foo: dichiara il metodo f()ma non ha una definizione

Quindi consiglierei semplicemente di rimuovere la extern templatedefinizione in quel caso particolare: è necessario aggiungerle solo se le classi sono completamente definite.

Per esempio:

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

compila e visualizza i simboli con 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

produzione:

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

e poi da man nmvediamo che Usignifica indefinito, quindi la definizione è rimasta solo TemplCppcome desiderato.

Tutto questo si riduce al compromesso delle dichiarazioni di intestazione complete:

  • aspetti positivi:
    • consente al codice esterno di utilizzare il nostro modello con nuovi tipi
    • abbiamo la possibilità di non aggiungere istanze esplicite se stiamo bene con il rigonfiamento degli oggetti
  • aspetti negativi:
    • quando si sviluppa quella classe, le modifiche all'implementazione dell'intestazione porteranno i sistemi di build intelligenti a ricostruire tutti gli includer, che potrebbero essere molti molti file
    • se vogliamo evitare il blocco del file oggetto, dobbiamo non solo fare istanze esplicite (come con le dichiarazioni di intestazione incomplete) ma anche aggiungere extern templatesu ogni includer, che i programmatori probabilmente dimenticheranno di fare

Ulteriori esempi di questi sono mostrati in: Istanziazione esplicita del modello: quando viene utilizzata?

Poiché il tempo di compilazione è così critico nei progetti di grandi dimensioni, consiglio vivamente dichiarazioni di modelli incomplete, a meno che parti esterne non abbiano assolutamente bisogno di riutilizzare il codice con le proprie complesse classi personalizzate.

E in tal caso, proverei prima a utilizzare il polimorfismo per evitare il problema del tempo di compilazione e utilizzare i modelli solo se è possibile ottenere notevoli miglioramenti delle prestazioni.

Testato in Ubuntu 18.04.


4

Il problema noto con i modelli è il rigonfiamento del codice, che è conseguenza della generazione della definizione di classe in ogni modulo che richiama la specializzazione del modello di classe. Per evitare ciò, a partire da C ++ 0x, si potrebbe utilizzare la parola chiave extern davanti alla specializzazione del modello di classe

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

L'istanziazione esplicita della classe template dovrebbe avvenire solo in una singola unità di traduzione, preferibile quella con definizione template (MyClass.cpp)

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

0

Se hai già utilizzato extern per le funzioni, per i modelli viene seguita esattamente la stessa filosofia. in caso contrario, potrebbe essere d'aiuto andare dall'esterno per funzioni semplici. Inoltre, potresti voler mettere gli extern nel file di intestazione e includere l'intestazione quando ne hai bisogno.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.