2009-11-12 28 views
73

sto ottenendo gli errori cercando di compilare una ++ classe template C che è diviso tra un .hpp e .cpp di file:Dividere le classi C++ basate su modelli in file .hpp/.cpp - è possibile?

$ g++ -c -o main.o main.cpp 
$ g++ -c -o stack.o stack.cpp 
$ g++ -o main main.o stack.o 
main.o: In function `main': 
main.cpp:(.text+0xe): undefined reference to 'stack<int>::stack()' 
main.cpp:(.text+0x1c): undefined reference to 'stack<int>::~stack()' 
collect2: ld returned 1 exit status 
make: *** [program] Error 1 

Ecco il mio codice:

stack.hpp:

#ifndef _STACK_HPP 
#define _STACK_HPP 

template <typename Type> 
class stack { 
    public: 
      stack(); 
      ~stack(); 
}; 
#endif 

stack.cpp:

#include <iostream> 
#include "stack.hpp" 

template <typename Type> stack<Type>::stack() { 
     std::cerr << "Hello, stack " << this << "!" << std::endl; 
} 

template <typename Type> stack<Type>::~stack() { 
     std::cerr << "Goodbye, stack " << this << "." << std::endl; 
} 

main.cpp:

#include "stack.hpp" 

int main() { 
    stack<int> s; 

    return 0; 
} 

ld è naturalmente corretto: i simboli non sono in stack.o.

La risposta a this question non aiuta, come sto già facendo come si dice.
This one potrebbe aiutare, ma non voglio spostare ogni singolo metodo nel file .hpp — Non avrei dovuto, dovrei?

È l'unica soluzione ragionevole per spostare tutto nel file .cpp nel file .hpp e includere semplicemente tutto, anziché collegarsi come file di oggetti autonomo? Quello sembra terribilmente brutto! In tal caso, potrei anche tornare al mio stato precedente e rinominare stack.cpp in stack.hpp e farlo con esso.

+0

Esistono due soluzioni ottimali per quando si desidera mantenere il codice nascosto (in un file binario) o tenerlo pulito. È necessario ridurre la generalità anche se nella prima situazione. È spiegato qui: http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file – Sheric

risposta

129

Non è possibile scrivere l'implementazione di una classe template in un file cpp separato e compilare. Tutti i modi per farlo, se qualcuno afferma, sono soluzioni alternative per simulare l'uso di un file cpp separato, ma praticamente se si intende scrivere una libreria di classi modello e distribuirla con i file header e lib per nascondere l'implementazione, semplicemente non è possibile .

Per sapere perché, diamo un'occhiata al processo di compilazione. I file di intestazione non vengono mai compilati. Sono solo pre-elaborati. Il codice preelaborato viene quindi battuto con il file cpp che è effettivamente compilato. Ora se il compilatore deve generare il layout di memoria appropriato per l'oggetto, è necessario conoscere il tipo di dati della classe template.

In realtà si deve capire che la classe template non è affatto una classe ma un template per una classe la cui dichiarazione e definizione viene generata dal compilatore in fase di compilazione dopo aver ottenuto dall'argomento le informazioni del tipo di dati. Finché non è possibile creare il layout di memoria, non è possibile generare le istruzioni per la definizione del metodo. Ricorda che il primo argomento del metodo di classe è l'operatore "this". Tutti i metodi di classe vengono convertiti in singoli metodi con il nome mangling e il primo parametro come oggetto su cui opera. L'argomento "this" indica in realtà la dimensione dell'oggetto che in classe template non è disponibile per il compilatore a meno che l'utente non istanzia l'oggetto con un argomento di tipo valido. In questo caso se metti le definizioni del metodo in un file cpp separato e provi a compilarlo, il file oggetto stesso non verrà generato con le informazioni sulla classe. La compilazione non fallirà, genererebbe il file oggetto ma non genererà alcun codice per la classe template nel file oggetto. Questo è il motivo per cui il linker non è in grado di trovare i simboli nei file oggetto e la compilazione fallisce.

Ora qual è l'alternativa per nascondere importanti dettagli di implementazione? Come tutti sappiamo, l'obiettivo principale alla base della separazione dell'interfaccia dall'implementazione consiste nel nascondere i dettagli di implementazione in forma binaria. Qui è dove devi separare le strutture dati e gli algoritmi. Le classi template devono rappresentare solo strutture dati e non algoritmi. Ciò consente di nascondere ulteriori dettagli di implementazione preziosi in librerie di classi separate non templatizzate, le classi all'interno delle quali funzionano sulle classi di template o semplicemente le usano per contenere i dati. La classe template in realtà conterrebbe meno codice per assegnare, ottenere e impostare i dati. Il resto del lavoro sarebbe svolto dalle classi dell'algoritmo.

Spero che questa discussione sia utile.

+1

"deve essere inteso che la classe template non è affatto una classe" - non era il contrario? Il modello di classe è un modello. "Template class" è talvolta utilizzato al posto di "istanziazione di un modello" e sarebbe una classe reale. – Xupicor

+0

Solo per riferimento, non è corretto dire che non ci sono soluzioni alternative! Separare le strutture dati dai metodi è anche una cattiva idea in quanto è contraria all'incapsulamento. C'è una soluzione eccezionale che puoi utilizzare in alcune situazioni (credo di più) qui: http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file – Sheric

+0

@Xupicor, hai ragione. Tecnicamente "Class Template" è ciò che scrivi in ​​modo da poter istanziare una "Template Class" e il suo oggetto corrispondente. Tuttavia, credo che in una terminologia generica, usando entrambi i termini in modo intercambiabile non sarebbe tutto sbagliato, la sintassi per la definizione del "Class Template" inizia con la parola "template" e non con "class". –

6

No, non è possibile. Non senza la parola chiave export, che a tutti gli effetti non esiste realmente.

Il meglio che puoi fare è mettere le tue implementazioni di funzione in un file ".tcc" o ".tpp", e # include il file .tcc alla fine del tuo file .hpp. Tuttavia questo è semplicemente cosmetico; è sempre lo stesso di implementare tutto nei file di intestazione. Questo è semplicemente il prezzo che si paga usando i template.

+3

La tua risposta non è corretta. Puoi generare codice da una classe template in un file cpp, dato che sai quali argomenti del template usare. Vedi la mia risposta per ulteriori informazioni. –

+0

Questo non è proprio vero. –

+2

Vero, ma ciò comporta la seria limitazione della necessità di aggiornare il file .cpp e ricompilare ogni volta che viene introdotto un nuovo tipo che utilizza il modello, che probabilmente non è quello che l'OP aveva in mente. –

0

Poiché i modelli vengono compilati quando richiesto, ciò impone una restrizione per i progetti multi-file: l'implementazione (definizione) di una classe o di una funzione modello deve essere nello stesso file della sua dichiarazione. Ciò significa che non possiamo separare l'interfaccia in un file di intestazione separato e che dobbiamo includere sia l'interfaccia che l'implementazione in qualsiasi file che utilizza i modelli.

0

È necessario disporre di tutto nel file hpp. Il problema è che le classi non vengono effettivamente create fino a quando il compilatore non vede che sono necessarie per qualche altro file cpp, quindi deve avere tutto il codice disponibile per compilare la classe basata su modelli in quel momento.

Una cosa che tendo a fare è provare a suddividere i miei modelli in una parte generica non basata su modelli (che può essere divisa tra cpp/hpp) e nella parte modello specifica del tipo che eredita la classe non basata su modelli.

1

Solo se si #include "stack.cpp alla fine di stack.hpp. Vorrei solo raccomandare questo approccio se l'implementazione è relativamente grande e se si rinomina il file .cpp in un'altra estensione, in modo da differenziarlo dal codice normale.

+3

Se stai facendo questo, ti consigliamo di aggiungere #ifndef STACK_CPP (e amici) al tuo file stack.cpp. –

+0

Beat me a questo suggerimento. Anche io non preferisco questo approccio per ragioni di stile. – luke

+1

Sì, in tal caso, il 2 ° file non deve assolutamente avere l'estensione 'cpp' (o' cc' o qualsiasi altra cosa) perché questo è in netto contrasto con il suo vero ruolo. Dovrebbe invece essere fornita un'estensione diversa che indica che è (A) un'intestazione e (B) un'intestazione da includere nel _bottom_ di un'altra intestazione. Io uso 'tpp' per questo, che può anche essere valido per' t'em'p'late im'p'lementation (definizioni out-of-line). Ho discusso di più su questo qui: http://stackoverflow.com/questions/1724036/splitting-templated-c-classes-into-hpp-cpp-files-is-it-possible/9992920#comment57362919_9992920 –

0

Un'altra possibilità è quella di fare qualcosa di simile:

#ifndef _STACK_HPP 
#define _STACK_HPP 

template <typename Type> 
class stack { 
    public: 
      stack(); 
      ~stack(); 
}; 

#include "stack.cpp" // Note the include. The inclusion 
         // of stack.h in stack.cpp must be 
         // removed to avoid a circular include. 

#endif 

Cosa non mi piace questo suggerimento per una questione di stile, ma può soddisfare voi.

+0

Il 2 ° header glorificato essendo incluso dovrebbe avere almeno un'estensione diversa da 'cpp' per evitare confusione con i file di origine _actual_. I suggerimenti comuni includono 'tpp' e' tcc'. –

0

La parola chiave "export" è il modo per separare l'implementazione del modello dalla dichiarazione del modello. Questo è stato introdotto nello standard C++ senza un'implementazione esistente. A tempo debito solo un paio di compilatori l'hanno effettivamente implementato. Leggi informazioni approfondite al Inform IT article on export

+1

Questa è quasi una risposta solo per collegamento e quel collegamento è morto. –

2

A volte è possibile avere la maggior parte dell'implementazione nascosta nel file cpp, se è possibile estrarre le funzionalità comuni da tutti i parametri del modello in una classe non template (possibilmente di tipo non sicuro). Quindi l'intestazione conterrà le chiamate di reindirizzamento a quella classe. Un approccio simile viene utilizzato quando si combatte con il problema "modello gonfia".

+0

+1 - anche se non funziona molto bene la maggior parte del tempo (almeno, non tutte le volte che desidero) – peterchen

2

Il problema è che un modello non genera una classe effettiva, è solo un modello che dice al compilatore come generare una classe. Devi generare una lezione concreta.

Il modo semplice e naturale consiste nel mettere i metodi nel file di intestazione. ma c'è un altro modo.

Nel file .cpp, se si dispone di un riferimento a ogni modello di istanza e metodo richiesto, il compilatore li genererà per l'utilizzo in tutto il progetto.

nuova stack.cpp:

#include <iostream> 
#include "stack.hpp" 
template <typename Type> stack<Type>::stack() { 
     std::cerr << "Hello, stack " << this << "!" << std::endl; 
} 
template <typename Type> stack<Type>::~stack() { 
     std::cerr << "Goodbye, stack " << this << "." << std::endl; 
} 
static void DummyFunc() { 
    static stack<int> stack_int; // generates the constructor and destructor code 
    // ... any other method invocations need to go here to produce the method code 
} 
+7

Non hai bisogno della funzione dummey: Usa 'template stack ;' Ciò impone un'istanza del modello nell'unità di compilazione corrente. Molto utile se si definisce un modello ma si desidera solo un paio di implementazioni specifiche in una lib condivisa. –

+0

@Martin: include tutte le funzioni membro? È fantastico. È necessario aggiungere questo suggerimento al thread "Funzioni nascoste di C++". –

+0

@LokiAstari Ho trovato un articolo su questo nel caso qualcuno volesse saperne di più: http: //www.cplusplus.com/forum/articles/14272/ –

2

Se si conosce che tipo il vostro stack verrà utilizzato con, è possibile creare un'istanza expicitly nel file cpp, e tenere tutto il codice relativo lì.

È anche possibile esportarli tramite DLL (!), Ma è piuttosto difficile ottenere la sintassi corretta (combinazioni specifiche MS di __declspec (dllexport) e la parola chiave export).

Lo abbiamo usato in una matematica/geom lib che ha un doppio/float con modelli, ma ha un bel po 'di codice. (Ho cercato su Google per ora, però non ho quel codice.)

74

È possibile, purché tu sappia quali istanze avrai bisogno.

Aggiungere il seguente codice alla fine del stack.cpp e funzionerà: saranno istanziati

template class stack<int>; 

Tutti i metodi non-modello di stack e passo che collega funzionerà bene.

+6

In pratica, molte persone usano un file cpp separato per questo - qualcosa come stackinstantiations.cpp. –

+0

@NemanjaTrifunovic puoi dare un esempio di come sarebbero gli stackinstantiations.cpp? – qwerty9967

+3

In realtà ci sono altre soluzioni: http://www.codeproject.com/Articles/48575/How-to-define-a-template-class-in-ah-file-and-imp – sleepsort

8

si può fare in questo modo

// xyz.h 
#ifndef _XYZ_ 
#define _XYZ_ 

template <typename XYZTYPE> 
class XYZ { 
    //Class members declaration 
}; 

#include "xyz.cpp" 
#endif 

//xyz.cpp 
#ifdef _XYZ_ 
//Class definition goes here 

#endif 

Questo è stato discusso in Daniweb

Anche in FAQ ma utilizzando C++ esportazione di parole chiave.

+3

'Includere un file' cpp' è in genere una pessima idea. anche se hai una ragione valida per questo, il file - che è in realtà solo un'intestazione glorificata - dovrebbe avere un 'hpp' o qualche estensione diversa (es.' tpp') per chiarire cosa sta succedendo, rimuovere la confusione intorno ' makefile's targeting _actual_ 'cpp' file, ecc. –

+0

@underscore_d Potresti spiegare perché includere un file' .cpp' è una pessima idea? – Abbas

+1

@Abbas perché l'estensione 'cpp' (o' cc', o 'c', o qualsiasi altra cosa) indica che il file è un pezzo dell'implementazione, che l'unità di traduzione risultante (output del preprocessore) è compilabile separatamente e che il il contenuto del file è compilato una sola volta. non indica che il file è una parte riutilizzabile dell'interfaccia, da includere arbitrariamente ovunque.'# include'ing un file _actual_' cpp' riempirebbe velocemente lo schermo con errori di definizione multipli, e giustamente. in questo caso, poiché c'è un motivo per "# includerlo", "cpp" era solo la scelta sbagliata dell'estensione. –

3

Credo che ci siano due ragioni principali per cercare di separare il codice basato su modelli in un colpo di testa e un cpp:

Uno è per mera eleganza. A tutti noi piace scrivere codice che è wasy da leggere, gestire ed è riutilizzabile in seguito.

Altro è la riduzione dei tempi di compilazione.

Attualmente sono (come sempre) software di simulazione di codifica in combinazione con OpenCL e ci piace mantenere il codice in modo che possa essere eseguito utilizzando i tipi float (cl_float) o double (cl_double) secondo necessità, a seconda della capacità HW. In questo momento ciò viene fatto usando un #define REAL all'inizio del codice, ma questo non è molto elegante. La modifica della precisione desiderata richiede la ricompilazione dell'applicazione. Dal momento che non ci sono tipi di runtime reali, dobbiamo vivere con questo per il momento. Fortunatamente i kernel OpenCL sono compilati in runtime e un semplice sizeof (REAL) ci consente di modificare di conseguenza il runtime del codice del kernel.

Il problema molto più grande è che anche se l'applicazione è modulare, quando si sviluppano classi ausiliarie (come quelle che precalcolano le costanti di simulazione) devono essere anche rappresentate. Queste classi appaiono tutte almeno una volta in cima all'albero delle dipendenze della classe, poiché la classe template finale avrà un'istanza di una di queste classi factory, il che significa che praticamente ogni volta che apporto una modifica secondaria alla classe factory, l'intero il software deve essere ricostruito. Questo è molto fastidioso, ma non riesco a trovare una soluzione migliore.

-3

Sto lavorando con Visual Studio 2010, se si desidera dividere i file .he cpp, includono l'intestazione cpp alla fine del file h

0

1) Ricordate il motivo principale per separare i file .h e .cpp è quello di nascondere l'implementazione della classe come un codice Obj compilato separatamente che può essere collegato al codice dell'utente che include un .h della classe.

2) Le classi non template hanno tutte le variabili definite in modo concreto e specifico nei file .h e .cpp. Quindi il compilatore avrà la necessità di informazioni su tutti i tipi di dati utilizzati nella classe prima della compilazione/traduzione  generando il codice oggetto/macchina Le classi modello non hanno informazioni sul tipo di dati specifico prima che l'utente della classe istanzia un oggetto che passa il richiesto tipo di dati:

 TClass<int> myObj; 

3) Solo dopo questa esemplificazione, il compilatore genera la versione specifica della classe template in base al tipo di dati passato (s).

4) Pertanto, .cpp NON può essere compilato separatamente senza conoscere il tipo di dati specifico dell'utente. Quindi deve rimanere come codice sorgente all'interno di ".h" finché l'utente non specifica il tipo di dati richiesto, quindi può essere generato su un tipo di dati specifico compilato

Problemi correlati