Vraag Waarom kunnen sjablonen alleen in het headerbestand worden geïmplementeerd?


Citaat van De C ++ standaardbibliotheek: een tutorial en een handboek:

De enige draagbare manier om sjablonen op dit moment te gebruiken, is om ze in headerbestanden te implementeren met behulp van inline-functies.

Waarom is dit?

(Verduidelijking: header-bestanden zijn niet de enkel en alleen draagbare oplossing. Maar ze zijn de handigste draagbare oplossing.)


1383
2018-01-30 10:06


oorsprong


antwoorden:


Het is niet nodig om de implementatie in het header-bestand te plaatsen, zie de alternatieve oplossing aan het einde van dit antwoord.

Hoe dan ook, de reden dat uw code faalt, is dat de compiler bij het instantiëren van een sjabloon een nieuwe klasse maakt met het opgegeven sjabloonargument. Bijvoorbeeld:

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

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

Bij het lezen van deze regel zal de compiler een nieuwe klasse maken (laten we het noemen FooInt), wat gelijk is aan het volgende:

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

Bijgevolg moet de compiler toegang hebben tot de implementatie van de methoden om ze te instantiëren met het sjabloonargument (in dit geval int). Als deze implementaties niet in de header stonden, zouden ze niet toegankelijk zijn en daarom zou de compiler de sjabloon niet kunnen instantiëren.

Een veel voorkomende oplossing hiervoor is om de sjabloonverklaring in een headerbestand te schrijven en vervolgens de klasse in een implementatiebestand (bijvoorbeeld .tpp) te implementeren en dit implementatiebestand aan het einde van de header op te nemen.

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

Op deze manier is de implementatie nog steeds gescheiden van de aangifte, maar is toegankelijk voor de compiler.

Een andere oplossing is om de implementatie gescheiden te houden en expliciet alle sjablooninstanties te maken die u nodig hebt:

// 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

Als mijn uitleg niet duidelijk genoeg is, kun je de C ++ Super-FAQ over dit onderwerp.


1206
2018-01-30 10:26



Veel goede antwoorden hier, maar ik wilde dit toevoegen (voor de volledigheid):

Als u aan de onderkant van het implementatie-cpp-bestand expliciete instantiatie uitvoert van alle typen waarmee de sjabloon zal worden gebruikt, kan de linker deze op de gebruikelijke manier vinden.

Bewerken: voorbeeld van een expliciete sjabloonconversie toevoegen. Wordt gebruikt nadat de sjabloon is gedefinieerd en alle lidfuncties zijn gedefinieerd.

template class vector<int>;

Dit zal de klasse en al zijn lidfuncties (alleen) instantiëren (en dus beschikbaar maken voor de linker). Een soortgelijke syntaxis werkt voor sjabloonfuncties, dus als u overbelastingen van niet-leden hebt, moet u mogelijk hetzelfde doen voor die functies.

Het bovenstaande voorbeeld is tamelijk nutteloos, omdat vector volledig is gedefinieerd in headers, behalve wanneer een algemeen include-bestand (voorgecompileerde header?) Wordt gebruikt extern template class vector<int> om te voorkomen dat het in het geheel in een schijnsel zet anders (1000?) Bestanden die vector gebruiken.


200
2017-08-13 13:49



Het is vanwege de vereiste voor afzonderlijke compilatie en omdat sjablonen instantiation-style polymorfisme zijn.

Laten we een beetje dichter bij beton komen voor een verklaring. Stel dat ik de volgende bestanden heb:

  • foo.h
    • verklaart de interface van class MyClass<T>
  • foo.cpp
    • definieert de implementatie van class MyClass<T>
  • bar.cpp
    • toepassingen MyClass<int>

Afzonderlijke compilatie betekent dat ik zou moeten kunnen compileren foo.cpp onafhankelijk van bar.cpp. De compiler doet al het harde werk van analyse, optimalisatie en codegeneratie op elke compilatie-eenheid volledig onafhankelijk; we hoeven geen analyse van het hele programma uit te voeren. Het is alleen de linker die het hele programma in één keer moet verwerken en de taak van de linker is aanzienlijk eenvoudiger.

bar.cpp hoeft niet eens te bestaan ​​als ik compileer foo.cpp, maar ik zou nog steeds de foo.o Ik had al samen met de bar.o Ik heb het pas gemaakt, zonder opnieuw te moeten compileren foo.cpp. foo.cpp zou zelfs kunnen worden gecompileerd tot een dynamische bibliotheek, ergens anders zonder verspreid foo.cpp, en gekoppeld aan code schrijven ze jaren nadat ik schreef foo.cpp.

"Instantiatiestijl polymorfisme" betekent dat de sjabloon MyClass<T> is niet echt een generieke klasse die kan worden gecompileerd om te coderen die voor elke waarde kan werken T. Dat zou overhead toevoegen zoals boksen, functiepunter doorgeven aan allocators en constructeurs, etc. De bedoeling van C ++ templates is om te voorkomen dat je bijna identiek moet schrijven class MyClass_int, class MyClass_float, enz., maar om nog steeds te kunnen eindigen met gecompileerde code die grotendeels is alsof wij had elke versie afzonderlijk geschreven. Dus een sjabloon is letterlijk een sjabloon; een klassensjabloon is niet een klasse, het is een recept voor het maken van een nieuwe klasse voor elk T we komen tegen. Een sjabloon kan niet in de code worden gecompileerd, alleen het resultaat van het instantiëren van de sjabloon kan worden gecompileerd.

Dus wanneer foo.cpp is gecompileerd, kan de compiler niet zien bar.cpp om te weten dat MyClass<int> is nodig. Het kan de sjabloon zien MyClass<T>, maar daarvoor kan geen code worden verzonden (het is een sjabloon, geen klasse). En wanneer bar.cpp is gecompileerd, kan de compiler zien dat deze een moet maken MyClass<int>, maar het kan de sjabloon niet zien MyClass<T> (alleen de interface in foo.h) zodat het het niet kan maken.

Als foo.cpp zelf gebruikt MyClass<int>, dan wordt er code voor gegenereerd tijdens het compileren foo.cpp, dus wanneer bar.o is gelinkt aan foo.o ze kunnen worden aangesloten en zullen werken. We kunnen dat feit gebruiken om een ​​eindige set sjabloon-instantiaties in een .cpp-bestand te implementeren door een enkele sjabloon te schrijven. Maar er is geen manier voor bar.cpp om de sjabloon te gebruiken als een sjabloon en maak het op wat voor soort dan ook; het kan alleen bestaande versies van de template-klasse gebruiken die de auteur van foo.cpp dacht te bieden.

Je zou kunnen denken dat de compiler bij het samenstellen van een sjabloon "alle versies" moet genereren, waarbij degene die nooit worden gebruikt tijdens het linken worden uitgefilterd. Afgezien van de enorme overhead en de extreme moeilijkheden waarmee een dergelijke aanpak te maken zou kunnen krijgen, omdat "type modifier" -functies zoals pointers en arrays het mogelijk maken dat zelfs alleen de ingebouwde typen een oneindig aantal typen mogelijk maken, wat gebeurt er als ik nu mijn programma uitbreid door toe te voegen:

  • baz.cpp
    • verklaart en implementeert class BazPrivateen gebruikt MyClass<BazPrivate>

Er is geen enkele manier dat dit zou kunnen werken, tenzij wij ook

  1. Moet opnieuw compileren foo.cpp elke keer dat we veranderen elk ander bestand in het programma, in het geval het een nieuwe nieuwe instantiatie van MyClass<T>
  2. Vereisen dat baz.cpp bevat (mogelijk via header bevat) de volledige sjabloon van MyClass<T>, zodat de compiler kan genereren MyClass<BazPrivate> tijdens compilatie van baz.cpp.

Niemand houdt van (1), omdat compilatiesystemen voor de volledige programma-analyse worden gebruikt voor altijd om te compileren en omdat het het onmogelijk maakt om gecompileerde bibliotheken te distribueren zonder de broncode. Dus we hebben (2) in plaats daarvan.


173
2018-05-11 03:54



Sjablonen moeten zijn geïnstantieerd door de compiler voordat ze daadwerkelijk worden gecompileerd in objectcode. Deze instantiatie kan alleen worden bereikt als de sjabloonargumenten bekend zijn. Stel je nu een scenario voor waarin een sjabloonfunctie wordt gedeclareerd a.h, gedefinieerd in a.cpp en gebruikt in b.cpp. Wanneer a.cpp is gecompileerd, is het niet noodzakelijk bekend dat de aankomende compilatie b.cpp vereist een instantie van de sjabloon, laat staan ​​welk specifiek exemplaar dat zou zijn. Voor meer header- en bronbestanden kan de situatie snel ingewikkelder worden.

Men kan stellen dat compilers slimmer gemaakt kunnen worden om "vooruit te kijken" voor alle gebruik van de sjabloon, maar ik ben er zeker van dat het niet moeilijk zou zijn om recursieve of anderszins gecompliceerde scenario's te maken. AFAIK, compilers doen niet zo'n vooruitblik. Zoals Anton opmerkt, ondersteunen sommige compilers expliciete exportaangiften van template-instantiations, maar niet alle compilers ondersteunen dit (nog?).


64
2018-01-30 10:23



In feite definieerden versies van de C ++ standaard voor C ++ 11 het 'export'-sleutelwoord zou maken het mogelijk om sjablonen eenvoudig in een headerbestand te declareren en elders te implementeren.

Helaas heeft geen van de populaire compilers dit zoekwoord geïmplementeerd. De enige die ik ken is de frontend geschreven door de Edison Design Group, die wordt gebruikt door de Comeau C ++ - compiler. Alle anderen drongen erop aan dat u sjablonen in headerbestanden schrijft, waarvoor de definitie van de code nodig is voor een goede instantiatie (zoals anderen al hebben opgemerkt).

Als gevolg hiervan heeft de ISO C ++ standaardcommissie besloten om de export kenmerk van sjablonen die beginnen met C ++ 11.


47
2018-01-30 13:38



Hoewel standaard C ++ geen dergelijke vereiste heeft, vereisen sommige compilers dat alle functie- en klassemodellen beschikbaar moeten worden gemaakt in elke vertaaleenheid die ze gebruiken. In feite moeten voor die compilers de instanties van sjabloonfuncties beschikbaar worden gemaakt in een headerbestand. Om te herhalen: dat betekent dat die compilers niet toestaan ​​dat ze worden gedefinieerd in niet-header-bestanden zoals .cpp-bestanden

Er is een exporteren sleutelwoord dat dit probleem zou moeten verzachten, maar het is nog lang niet overdraagbaar.


31
2018-01-30 10:15



Sjablonen moeten worden gebruikt in headers omdat de compiler verschillende versies van de code moet converteren, afhankelijk van de parameters die zijn opgegeven / afgeleid voor sjabloonparameters. Vergeet niet dat een sjabloon niet rechtstreeks een code vertegenwoordigt, maar een sjabloon voor verschillende versies van die code. Wanneer u een niet-sjabloonfunctie compileert in a .cppbestand, u stelt een concrete functie / klasse samen. Dit is niet het geval voor sjablonen, die kunnen worden geïnstantieerd met verschillende typen, namelijk, betoncode moet worden geëmitteerd wanneer sjabloonparameters worden vervangen door betonsoorten.

Er was een functie met de export sleutelwoord dat bedoeld was om te worden gebruikt voor afzonderlijke compilatie. De export functie is verouderd C++11 en AFAIK, slechts één compiler implementeerde het. Je moet geen gebruik maken van export. Aparte compilatie is niet mogelijk in C++ of C++11 maar misschien binnen C++17als concepten erin slagen, kunnen we op een of andere manier afzonderlijke compilaties maken.

Om een ​​afzonderlijke compilatie te bereiken, moet afzonderlijke controle van het sjabloonlichaam mogelijk zijn. Het lijkt erop dat een oplossing mogelijk is met concepten. Kijk hier eens even naar papier onlangs gepresenteerd op de norm commissie vergadering. Ik denk dat dit niet de enige vereiste is, omdat je nog steeds code moet converteren voor de sjablooncode in gebruikerscode.

Het afzonderlijke compilatieprobleem voor sjablonen Ik denk dat het ook een probleem is dat zich voordoet bij de migratie naar modules, waaraan momenteel wordt gewerkt.


26
2018-05-12 16:42



Dit betekent dat de meest draagbare manier om methode-implementaties van sjabloonklassen te definiëren, is om ze binnen de definitie van de sjabloonklasse te definiëren.

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};

13
2018-01-30 10:53