Memorizzazione delle definizioni delle funzioni del modello C ++ in un file .CPP

StackOverflow https://stackoverflow.com/questions/115703

  •  02-07-2019
  •  | 
  •  

Domanda

Ho un codice modello che preferirei archiviare in un file CPP anziché inline nell'intestazione. So che questo può essere fatto fintanto che sai quali tipi di template verranno utilizzati. Ad esempio:

.h file

class foo
{
public:
    template <typename T>
    void do(const T& t);
};

.cpp file

template <typename T>
void foo::do(const T& t)
{
    // Do something with t
}

template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);

Nota le ultime due righe: la funzione template foo :: do viene usata solo con ints e std :: stringhe, quindi queste definizioni indicano che l'app si collegherà.

La mia domanda è: è un brutto trucco o funzionerà con altri compilatori / linker? Al momento sto solo usando questo codice con VS2008 ma vorrò portarlo su altri ambienti.

È stato utile?

Soluzione

Il problema che descrivi può essere risolto definendo il modello nell'intestazione o tramite l'approccio descritto sopra.

Consiglio di leggere i seguenti punti dalla Domande frequenti sul C ++ Lite :

Vengono introdotti molti dettagli su questi (e altri) problemi relativi ai modelli.

Altri suggerimenti

Per gli altri in questa pagina che si chiedono quale sia la sintassi corretta (come ho fatto io) per la specializzazione esplicita del modello (o almeno in VS2008), è la seguente ...

Nel tuo file .h ...

template<typename T>
class foo
{
public:
    void bar(const T &t);
};

E nel tuo file .cpp

template <class T>
void foo<T>::bar(const T &t)
{ }

// Explicit template instantiation
template class foo<int>;

Questo codice è ben formato. Devi solo prestare attenzione al fatto che la definizione del modello è visibile nel punto di istanza. Per citare lo standard, & # 167; 14.7.2.4:

  

La definizione di un modello di funzione non esportato, un modello di funzione membro non esportato o una funzione membro non esportata o un membro di dati statici di un modello di classe deve essere presente in ogni unità di traduzione in cui è esplicitamente istanziato.

Questo dovrebbe funzionare bene ovunque siano supportati i template. L'istanza esplicita del modello fa parte dello standard C ++.

Il tuo esempio è corretto ma non molto portatile. C'è anche una sintassi leggermente più pulita che può essere utilizzata (come sottolineato da @ namespace-sid).

Supponiamo che la classe modello sia parte di alcune librerie che devono essere condivise. Dovrebbero essere compilate altre versioni della classe modello? Il manutentore della biblioteca dovrebbe anticipare tutti gli usi possibili della classe?

Un approccio alternativo è una leggera variazione di ciò che hai: aggiungi un terzo file che è il file di implementazione / istanza del modello.

file foo.h

// Standard header file guards omitted

template <typename T>
class foo
{
public:
    void bar(const T& t);
};

file foo.cpp

// Always include your headers
#include "foo.h"

template <typename T>
void foo::bar(const T& t)
{
    // Do something with t
}

file foo-impl.cpp

// Yes, we include the .cpp file
#include "foo.cpp"
template class foo<int>;

L'unica avvertenza è che devi dire al compilatore di compilare foo-impl.cpp invece di foo.cpp poiché la compilazione di quest'ultimo non fa nulla.

Naturalmente, puoi avere più implementazioni nel terzo file o avere più file di implementazione per ogni tipo che desideri utilizzare.

Ciò consente molta più flessibilità quando si condivide la classe di template per altri usi.

Questa configurazione riduce anche i tempi di compilazione per le classi riutilizzate perché non stai ricompilando lo stesso file di intestazione in ogni unità di traduzione.

Questo non è sicuramente un brutto trucco, ma fai attenzione al fatto che dovrai farlo (la specializzazione esplicita del modello) per ogni classe / tipo che vuoi usare con il modello dato. In caso di MOLTI tipi che richiedono l'istanza del modello, nel file .cpp possono essere presenti MOLTE righe. Per risolvere questo problema puoi avere un TemplateClassInst.cpp in ogni progetto che usi in modo da avere un maggiore controllo su quali tipi verranno istanziati. Ovviamente questa soluzione non sarà perfetta (alias proiettile d'argento) in quanto potresti finire per rompere l'ODR :).

Esiste, nell'ultimo standard, una parola chiave (export) che contribuirebbe ad alleviare questo problema, ma non è implementata in nessun compilatore di cui sono a conoscenza, tranne Comeau.

Vedi le FAQ-lite su questo .

Sì, questo è il modo standard per creare un'istanza esplicita specializiation . Come hai affermato, non puoi creare un'istanza di questo modello con altri tipi.

Modifica: corretto in base al commento.

Questo è un modo standard per definire le funzioni del modello. Penso che ci siano tre metodi che ho letto per definire i template. O probabilmente 4. Ognuno con pro e contro.

  1. Definisce nella definizione della classe. Non mi piace affatto perché penso che le definizioni di classe siano strettamente di riferimento e dovrebbero essere facili da leggere. Tuttavia, è molto meno complicato definire i modelli in classe che all'esterno. E non tutte le dichiarazioni dei modelli sono allo stesso livello di complessità. Questo metodo rende anche il modello un modello vero.

  2. Definisce il modello nella stessa intestazione, ma al di fuori della classe. Questo è il mio modo preferito la maggior parte delle volte. Mantiene in ordine la definizione della tua classe, il modello rimane un modello vero. Richiede tuttavia la denominazione completa del modello che può essere complicata. Inoltre, il tuo codice è disponibile per tutti. Ma se hai bisogno che il tuo codice sia in linea, questo è l'unico modo. Puoi anche farlo creando un file .INL alla fine delle definizioni della classe.

  3. Includi header.h e deployment.CPP nel tuo main.CPP. Penso che sia così. Non dovrai preparare alcuna pre-istanza, si comporterà come un vero modello. Il problema che ho con esso è che non è naturale. Normalmente non includiamo e prevediamo di includere i file di origine. Immagino che dal momento che hai incluso il file sorgente, le funzioni del modello possano essere integrate.

  4. Quest'ultimo metodo, che era il modo pubblicato, sta definendo i template in un file sorgente, proprio come il numero 3; ma invece di includere il file sorgente, pre-istanziamo i modelli a quelli di cui avremo bisogno. Non ho problemi con questo metodo ed è utile a volte. Abbiamo un grosso codice, non può trarre vantaggio dall'essere inline quindi inseriscilo in un file CPP. E se conosciamo le istanze comuni e possiamo predefinirle. Questo ci salva dallo scrivere praticamente la stessa cosa 5, 10 volte. Questo metodo ha il vantaggio di mantenere il nostro codice proprietario. Ma non consiglio di inserire minuscole funzioni regolarmente utilizzate nei file CPP. Poiché ciò ridurrà le prestazioni della tua libreria.

Nota, non sono a conoscenza delle conseguenze di un file obj gonfio.

Non c'è niente di sbagliato nell'esempio che hai fornito. Ma devo dire che credo che non sia efficiente memorizzare le definizioni delle funzioni in un file cpp. Comprendo solo la necessità di separare la dichiarazione e la definizione della funzione.

Se utilizzata insieme all'istanza esplicita della classe, la libreria di controllo del concetto Boost (BCCL) può aiutarti a generare il codice funzione modello nei file cpp.

Tempo per un aggiornamento! Crea un file inline (.inl, o probabilmente qualsiasi altro) e copia semplicemente tutte le tue definizioni in esso. Assicurati di aggiungere il modello sopra ogni funzione (template <typename T, ...>). Ora invece di includere il file header nel file inline fai il contrario. Includi il file inline dopo la dichiarazione della tua classe (#include "file.inl").

Non so davvero perché nessuno lo abbia menzionato. Non vedo svantaggi immediati.

Facciamo un esempio, diciamo per qualche motivo che vuoi avere una classe template:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

Se compili questo codice con Visual Studio, funziona immediatamente. gcc produrrà un errore del linker (se lo stesso file di intestazione viene utilizzato da più file .cpp):

error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here

È possibile spostare l'implementazione nel file .cpp, ma è necessario dichiarare la classe in questo modo -

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test();

template <>
void DemoT<bool>::test();

// Instantiate parametrized template classes, implementation resides on .cpp side.
template class DemoT<bool>;
template class DemoT<int>;

E poi .cpp apparirà così:

//test_template.cpp:
#include "test_template.h"

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

Senza le ultime due righe nel file di intestazione - gcc funzionerà bene, ma Visual Studio produrrà un errore:

 error LNK2019: unresolved external symbol "public: void __cdecl DemoT<int>::test(void)" (?test@?$DemoT@H@@QEAAXXZ) referenced in function

La sintassi della classe modello è facoltativa nel caso in cui si desideri esporre la funzione tramite l'esportazione .dll, ma questo è applicabile solo per la piattaforma Windows, quindi test_template.h potrebbe apparire così:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

#ifdef _WIN32
    #define DLL_EXPORT __declspec(dllexport) 
#else
    #define DLL_EXPORT
#endif

template <>
void DLL_EXPORT DemoT<int>::test();

template <>
void DLL_EXPORT DemoT<bool>::test();

con il file .cpp dell'esempio precedente.

Questo però dà più mal di testa al linker, quindi si consiglia di usare l'esempio precedente se non si esporta la funzione .dll.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top