Perché i modelli possono essere implementati solo nel file di intestazione?

Jan 30 2009

Citazione dalla libreria standard C ++: un tutorial e un manuale :

L'unico modo portatile di utilizzare i modelli al momento è implementarli nei file di intestazione utilizzando le funzioni inline.

Perchè è questo?

(Chiarimento: i file di intestazione non sono l' unica soluzione portatile. Ma sono la soluzione portatile più conveniente.)

Risposte

1635 LucTouraille Jan 30 2009 at 17:26

Avvertenza: non è necessario inserire l'implementazione nel file di intestazione, vedere la soluzione alternativa alla fine di questa risposta.

Ad ogni modo, il motivo per cui il codice non funziona è che, quando si istanzia un modello, il compilatore crea una nuova classe con l'argomento del modello specificato. Per esempio:

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

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

Durante la lettura di questa riga, il compilatore creerà una nuova classe (chiamiamola FooInt), che è equivalente alla seguente:

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

Di conseguenza, il compilatore deve avere accesso all'implementazione dei metodi, per istanziarli con l'argomento template (in questo caso int). Se queste implementazioni non fossero nell'intestazione, non sarebbero accessibili e quindi il compilatore non sarebbe in grado di istanziare il modello.

Una soluzione comune a questo è scrivere la dichiarazione del modello in un file di intestazione, quindi implementare la classe in un file di implementazione (ad esempio .tpp) e includere questo file di implementazione alla fine dell'intestazione.

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
}

In questo modo, l'implementazione è ancora separata dalla dichiarazione, ma è accessibile al compilatore.

Soluzione alternativa

Un'altra soluzione è mantenere l'implementazione separata e creare un'istanza esplicita di tutte le istanze del modello necessarie:

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

Se la mia spiegazione non è abbastanza chiara, puoi dare un'occhiata alle Super-FAQ C ++ su questo argomento .

271 Ben May 11 2013 at 10:54

È a causa della necessità di una compilazione separata e perché i modelli sono polimorfismo in stile istanziazione.

Andiamo un po 'più vicini al concreto per una spiegazione. Diciamo che ho i seguenti file:

  • foo.h
    • dichiara l'interfaccia di class MyClass<T>
  • foo.cpp
    • definisce l'implementazione di class MyClass<T>
  • bar.cpp
    • usi MyClass<int>

La compilazione separata significa che dovrei essere in grado di compilare foo.cpp indipendentemente da bar.cpp . Il compilatore svolge tutto il duro lavoro di analisi, ottimizzazione e generazione di codice su ciascuna unità di compilazione in modo completamente indipendente; non abbiamo bisogno di fare analisi dell'intero programma. È solo il linker che deve gestire l'intero programma contemporaneamente e il lavoro del linker è sostanzialmente più semplice.

bar.cpp non ha nemmeno bisogno di esistere quando compilo foo.cpp , ma dovrei comunque essere in grado di collegare foo.o che avevo già insieme a bar.o l' ho appena prodotto, senza bisogno di ricompilare foo .cpp . foo.cpp potrebbe anche essere compilato in una libreria dinamica, distribuito altrove senza foo.cpp e collegato con il codice che scrivono anni dopo che ho scritto foo.cpp .

"Polimorfismo in stile istanziazione" significa che il modello MyClass<T>non è in realtà una classe generica che può essere compilata in codice che può funzionare per qualsiasi valore di T. Che dovrebbe aggiungere in testa, come la boxe, la necessità di passare puntatori a funzione di ripartitori e costruttori, ecc l'intenzione di template C ++ è quello di evitare di dover scrivere quasi identico class MyClass_int, class MyClass_floatecc, ma di essere ancora in grado di finire con il codice compilato che è per lo più come se ci fossimo scritto ogni versione separatamente. Quindi un modello è letteralmente un modello; un modello di classe non è una classe, è una ricetta per creare una nuova classe per ogni classe Tche incontriamo. Un modello non può essere compilato in codice, solo il risultato della creazione di un'istanza del modello può essere compilato.

Quindi, quando foo.cpp viene compilato, il compilatore non può vedere bar.cpp per sapere che MyClass<int>è necessario. Può vedere il modello MyClass<T>, ma non può emettere codice per quello (è un modello, non una classe). E quando bar.cpp viene compilato, il compilatore può vedere che ha bisogno di creare un MyClass<int>, ma non può vedere il modello MyClass<T>(solo la sua interfaccia in foo.h ) quindi non può crearlo.

Se foo.cpp stesso usa MyClass<int>, il codice verrà generato durante la compilazione di foo.cpp , quindi quando bar.o è collegato a foo.o possono essere collegati e funzioneranno. Possiamo usare questo fatto per consentire l'implementazione di un insieme finito di istanze di modelli in un file .cpp scrivendo un singolo modello. Ma non c'è modo per bar.cpp di usare il template come template e istanziarlo su qualunque tipo preferisca; può usare solo versioni preesistenti della classe basata su modelli che l'autore di foo.cpp ha pensato di fornire.

Si potrebbe pensare che durante la compilazione di un modello il compilatore debba "generare tutte le versioni", con quelle che non vengono mai utilizzate vengono filtrate durante il collegamento. A parte l'enorme sovraccarico e le estreme difficoltà che un simile approccio dovrebbe affrontare perché le funzionalità di "modificatore di tipo" come i puntatori e gli array consentono anche solo ai tipi incorporati di dare origine a un numero infinito di tipi, cosa succede quando estendo il mio programma aggiungendo:

  • baz.cpp
    • dichiara e implementa class BazPrivatee utilizzaMyClass<BazPrivate>

Non è possibile che questo funzioni a meno che non lo facciamo neanche noi

  1. Devo ricompilare foo.cpp ogni volta che cambiamo qualsiasi altro file nel programma , nel caso in cui aggiungesse una nuova istanza nuova diMyClass<T>
  2. Richiede che baz.cpp contenga (possibilmente tramite header include) il modello completo di MyClass<T>, in modo che il compilatore possa generare MyClass<BazPrivate>durante la compilazione di baz.cpp .

A nessuno piace (1), perché i sistemi di compilazione dell'analisi dell'intero programma impiegano un'eternità a compilarsi e perché rende impossibile distribuire le librerie compilate senza il codice sorgente. Quindi abbiamo (2) invece.

252 MaHuJa Aug 13 2009 at 20:49

Molte risposte corrette qui, ma volevo aggiungere questo (per completezza):

Se, in fondo al file cpp dell'implementazione, esegui un'istanza esplicita di tutti i tipi con cui verrà utilizzato il modello, il linker sarà in grado di trovarli come al solito.

Modifica: aggiunta di esempi di istanze di modelli esplicite. Utilizzato dopo che il modello è stato definito e tutte le funzioni membro sono state definite.

template class vector<int>;

Ciò istanzerà (e quindi renderà disponibile al linker) la classe e tutte le sue funzioni membro (solo). La sintassi simile funziona per le funzioni del modello, quindi se hai sovraccarichi di operatori non membri potresti dover fare lo stesso per quelli.

L'esempio sopra è abbastanza inutile poiché il vettore è completamente definito nelle intestazioni, tranne quando viene utilizzato un file di inclusione comune (intestazione precompilata?) In extern template class vector<int>modo da impedirne l'istanza in tutti gli altri (1000?) File che utilizzano il vettore.

86 DavidHanak Jan 30 2009 at 17:23

I modelli devono essere istanziati dal compilatore prima di compilarli effettivamente nel codice oggetto. Questa istanza può essere ottenuta solo se gli argomenti del modello sono noti. Ora immagina uno scenario in cui una funzione modello viene dichiarata a.h, definita a.cppe utilizzata in b.cpp. Quando a.cppviene compilato, non è necessariamente noto che la prossima compilazione b.cpprichiederà un'istanza del modello, per non parlare di quale specifica istanza sarebbe. Per più file di intestazione e di origine, la situazione può diventare rapidamente più complicata.

Si può sostenere che i compilatori possono essere resi più intelligenti per "guardare avanti" per tutti gli usi del modello, ma sono sicuro che non sarebbe difficile creare scenari ricorsivi o altrimenti complicati. AFAIK, i compilatori non guardano così avanti. Come ha sottolineato Anton, alcuni compilatori supportano dichiarazioni di esportazione esplicite di istanze di modelli, ma non tutti i compilatori lo supportano (ancora?).

65 DevSolar Jan 30 2009 at 20:38

In realtà, prima di C ++ 11 lo standard definiva la exportparola chiave che avrebbe reso possibile dichiarare i modelli in un file di intestazione e implementarli altrove.

Nessuno dei compilatori popolari ha implementato questa parola chiave. L'unico che conosco è il frontend scritto da Edison Design Group, che viene utilizzato dal compilatore Comeau C ++. Tutti gli altri richiedevano di scrivere modelli nei file di intestazione, perché il compilatore necessita della definizione del modello per una corretta istanziazione (come altri hanno già sottolineato).

As a result, the ISO C++ standard committee decided to remove the export feature of templates with C++11.

34 AntonGogolev Jan 30 2009 at 17:15

Although standard C++ has no such requirement, some compilers require that all function and class templates need to be made available in every translation unit they are used. In effect, for those compilers, the bodies of template functions must be made available in a header file. To repeat: that means those compilers won't allow them to be defined in non-header files such as .cpp files

There is an export keyword which is supposed to mitigate this problem, but it's nowhere close to being portable.

29 GermánDiago May 12 2013 at 23:42

Templates must be used in headers because the compiler needs to instantiate different versions of the code, depending on the parameters given/deduced for template parameters. Remember that a template doesn't represent code directly, but a template for several versions of that code. When you compile a non-template function in a .cpp file, you are compiling a concrete function/class. This is not the case for templates, which can be instantiated with different types, namely, concrete code must be emitted when replacing template parameters with concrete types.

There was a feature with the export keyword that was meant to be used for separate compilation. The export feature is deprecated in C++11 and, AFAIK, only one compiler implemented it. You shouldn't make use of export. Separate compilation is not possible in C++ or C++11 but maybe in C++17, if concepts make it in, we could have some way of separate compilation.

For separate compilation to be achieved, separate template body checking must be possible. It seems that a solution is possible with concepts. Take a look at this paper recently presented at the standards commitee meeting. I think this is not the only requirement, since you still need to instantiate code for the template code in user code.

The separate compilation problem for templates I guess it's also a problem that is arising with the migration to modules, which is currently being worked.

EDIT: As of August 2020 Modules are already a reality for C++: https://en.cppreference.com/w/cpp/language/modules

18 lafrecciablu May 12 2016 at 21:02

Even though there are plenty of good explanations above, I'm missing a practical way to separate templates into header and body.
My main concern is avoiding recompilation of all template users, when I change its definition.
Having all template instantiations in the template body is not a viable solution for me, since the template author may not know all if its usage and the template user may not have the right to modify it.
I took the following approach, which works also for older compilers (gcc 4.3.4, aCC A.03.13).

For each template usage there's a typedef in its own header file (generated from the UML model). Its body contains the instantiation (which ends up in a library which is linked in at the end).
Each user of the template includes that header file and uses the typedef.

A schematic example:

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

This way only the template instantiations will need to be recompiled, not all template users (and dependencies).

16 Benoît Jan 30 2009 at 17:53

It means that the most portable way to define method implementations of template classes is to define them inside the template class definition.

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};
9 Nikos Jul 19 2018 at 07:49

Just to add something noteworthy here. One can define methods of a templated class just fine in the implementation file when they are not function templates.


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;

    ...
}
7 EricShaw Jul 27 2016 at 12:01

If the concern is the extra compilation time and binary size bloat produced by compiling the .h as part of all the .cpp modules using it, in many cases what you can do is make the template class descend from a non-templatized base class for non type-dependent parts of the interface, and that base class can have its implementation in the .cpp file.

6 Robert Sep 17 2011 at 10:40

That is exactly correct because the compiler has to know what type it is for allocation. So template classes, functions, enums,etc.. must be implemented as well in the header file if it is to be made public or part of a library (static or dynamic) because header files are NOT compiled unlike the c/cpp files which are. If the compiler doesn't know the type is can't compile it. In .Net it can because all objects derive from the Object class. This is not .Net.

6 MosheRabaev Jul 19 2016 at 08:10

The compiler will generate code for each template instantiation when you use a template during the compilation step. In the compilation and linking process .cpp files are converted to pure object or machine code which in them contains references or undefined symbols because the .h files that are included in your main.cpp have no implementation YET. These are ready to be linked with another object file that defines an implementation for your template and thus you have a full a.out executable.

However since templates need to be processed in the compilation step in order to generate code for each template instantiation that you define, so simply compiling a template separate from it's header file won't work because they always go hand and hand, for the very reason that each template instantiation is a whole new class literally. In a regular class you can separate .h and .cpp because .h is a blueprint of that class and the .cpp is the raw implementation so any implementation files can be compiled and linked regularly, however using templates .h is a blueprint of how the class should look not how the object should look meaning a template .cpp file isn't a raw regular implementation of a class, it's simply a blueprint for a class, so any implementation of a .h template file can't be compiled because you need something concrete to compile, templates are abstract in that sense.

Therefore templates are never separately compiled and are only compiled wherever you have a concrete instantiation in some other source file. However, the concrete instantiation needs to know the implementation of the template file, because simply modifying the typename T using a concrete type in the .h file is not going to do the job because what .cpp is there to link, I can't find it later on because remember templates are abstract and can't be compiled, so I'm forced to give the implementation right now so I know what to compile and link, and now that I have the implementation it gets linked into the enclosing source file. Basically, the moment I instantiate a template I need to create a whole new class, and I can't do that if I don't know how that class should look like when using the type I provide unless I make notice to the compiler of the template implementation, so now the compiler can replace T with my type and create a concrete class that's ready to be compiled and linked.

To sum up, templates are blueprints for how classes should look, classes are blueprints for how an object should look. I can't compile templates separate from their concrete instantiation because the compiler only compiles concrete types, in other words, templates at least in C++, is pure language abstraction. We have to de-abstract templates so to speak, and we do so by giving them a concrete type to deal with so that our template abstraction can transform into a regular class file and in turn, it can be compiled normally. Separating the template .h file and the template .cpp file is meaningless. It is nonsensical because the separation of .cpp and .h only is only where the .cpp can be compiled individually and linked individually, with templates since we can't compile them separately, because templates are an abstraction, therefore we are always forced to put the abstraction always together with the concrete instantiation where the concrete instantiation always has to know about the type being used.

Meaning typename T get's replaced during the compilation step not the linking step so if I try to compile a template without T being replaced as a concrete value type that is completely meaningless to the compiler and as a result object code can't be created because it doesn't know what T is.

It is technically possible to create some sort of functionality that will save the template.cpp file and switch out the types when it finds them in other sources, I think that the standard does have a keyword export that will allow you to put templates in a separate cpp file but not that many compilers actually implement this.

Just a side note, when making specializations for a template class, you can separate the header from the implementation because a specialization by definition means that I am specializing for a concrete type that can be compiled and linked individually.

4 Pranay May 13 2017 at 08:42

A way to have separate implementation is as follows.

//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 has the forward declarations. foo.tpp has the implementation and include inner_foo.h; and foo.h will have just one line, to include foo.tpp.

On compile time, contents of foo.h are copied to foo.tpp and then the whole file is copied to foo.h after which it compiles. This way, there is no limitations, and the naming is consistent, in exchange for one extra file.

I do this because static analyzers for the code break when it does not see the forward declarations of class in *.tpp. This is annoying when writing code in any IDE or using YouCompleteMe or others.

1 Juan Feb 18 2020 at 03:30

I suggest looking at this gcc page which discusses the tradeoffs between the "cfront" and "borland" model for template instantiations.

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

The "borland" model corresponds to what the author suggests, providing the full template definition, and having things compiled multiple times.

It contains explicit recommendations concerning using manual and automatic template instantiation. For example, the "-repo" option can be used to collect templates which need to be instantiated. Or another option is to disable automatic template instantiations using "-fno-implicit-templates" to force manual template instantiation.

In my experience, I rely on the C++ Standard Library and Boost templates being instantiated for each compilation unit (using a template library). For my large template classes, I do manual template instantiation, once, for the types I need.

This is my approach because I am providing a working program, not a template library for use in other programs. The author of the book, Josuttis, works a lot on template libraries.

If I was really worried about speed, I suppose I would explore using Precompiled Headers https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html

which is gaining support in many compilers. However, I think precompiled headers would be difficult with template header files.