Separare automaticamente le definizioni di classe dalle dichiarazioni?
-
19-08-2019 - |
Domanda
Sto usando una libreria che consiste quasi interamente di classi e funzioni basate su modelli nei file di intestazione , in questo modo:
// foo.h
template<class T>
class Foo {
Foo(){}
void computeXYZ() { /* heavy code */ }
};
template<class T>
void processFoo(const Foo<T>& foo) { /* more heavy code */ }
Ora questo è negativo perché i tempi di compilazione sono insopportabili ogni volta che includo uno di quei file di intestazione (e in realtà ne includo molti in ciascuna delle mie unità di compilazione).
Dato che come parametro modello uso solo uno o due tipi, intendo creare, per ogni file di intestazione della libreria, un file che contiene solo dichiarazioni , senza il codice pesante, come questo:
// NEW: fwd-foo.h
template<class T>
class Foo {
Foo();
void computeXYZ();
};
template<class T>
void processFoo(const Foo<T>& foo);
E poi un file che crea tutte le istanze di cui avrò bisogno. Quel file può essere compilato separatamente una volta per tutte :
// NEW: foo.cpp
#include "foo.h"
template class Foo<int>;
template class Foo<double>;
template void processFoo(const Foo<int>& foo);
template void processFoo(const Foo<double>& foo);
Ora posso solo includere fwd-foo.h
nel mio codice e avere brevi tempi di compilazione. Alla fine collegherò contro foo.o
.
Il rovescio della medaglia, ovviamente, è che devo creare questi nuovi file fwd-foo.h
e foo.cpp
. E ovviamente è un problema di manutenzione: quando viene rilasciata una nuova versione della libreria, devo adattarle a quella nuova versione. Ci sono altri aspetti negativi?
E la mia domanda principale è:
È possibile creare questi nuovi file, in particolare fwd-foo.h
, automaticamente dall'originale foo.h
? Devo farlo per molti file di intestazione della libreria (forse 20 o giù di lì), e una soluzione automatica sarebbe la migliore soprattutto nel caso in cui una nuova versione della libreria venga rilasciata e devo farlo di nuovo con la nuova versione. Sono disponibili strumenti per questa attività?
MODIFICA:
Domanda aggiuntiva: in che modo la parola chiave extern
recentemente supportata può aiutarmi in questo caso?
Soluzione
Utilizziamo lzz che divide un singolo file in un'intestazione e un'unità di traduzione separate. Per impostazione predefinita, normalmente inserisce anche le definizioni del modello nell'intestazione, tuttavia è possibile specificare che non si desidera che ciò accada.
Per mostrarti come potresti usarlo, considera quanto segue:
// t.cc
#include "b.h"
#include "c.h"
template <typename T>
class A {
void foo () {
C c;
c.foo ();
b.foo ();
}
B b;
}
Prendi il file sopra e copialo nel file 't.lzz'. Inserisci le direttive #include in blocchi $ hdr e $ src separati, se necessario:
// t.lzz
$hdr
#include "b.h"
$end
$src
#include "c.h"
$end
template <typename T>
class A {
void foo () {
C c;
c.foo ();
b.foo ();
}
B b;
}
Ora infine, esegui lzz sul file specificando che inserisce le definizioni del modello nel file di origine. Puoi farlo usando un $ pragma nel file sorgente, oppure puoi usare l'opzione della riga di comando " -ts " ;:
Ciò comporterà la generazione dei seguenti file:
// t.h
//
#ifndef LZZ_t_h
#define LZZ_t_h
#include "b.h"
#undef LZZ_INLINE
#ifdef LZZ_ENABLE_INLINE
#define LZZ_INLINE inline
#else
#define LZZ_INLINE
#endif
template <typename T>
class A
{
void foo ();
B b;
};
#undef LZZ_INLINE
#endif
E
// t.cpp
//
#include "t.h"
#include "c.h"
#define LZZ_INLINE inline
template <typename T>
void A <T>::foo ()
{
C c;
c.foo ();
b.foo ();
}
#undef LZZ_INLINE
È quindi possibile eseguirli tramite alcuni comandi grep / sed per rimuovere le macro dell'helper LZZ.
Altri suggerimenti
Prova a usare le intestazioni precompilate. So che GCC e MSVC supportano questa funzione. L'utilizzo è specifico del venditore, tuttavia.
Sto lavorando allo stesso problema da un po 'di tempo ormai. Nella soluzione che stai proponendo, stai definendo le tue classi di template due volte. Andrà bene se definisce le stesse cose (nello stesso ordine), ma prima o poi avrai problemi.
Quello che ho escogitato è di considerare il problema al contrario. Finché non stai specializzando la tua implementazione, funziona con grazia.
Utilizza due macro, che devono aggiornare gli argomenti del modello nel file di implementazione (attenzione, tuttavia, se si desidera aggiungere argomenti del modello predefiniti alla classe).
// foo.h
#define FOO_TEMPLATE template<typename T>
#define FOO_CLASS Foo<T>
FOO_TEMPLATE
class Foo {
Foo();
void computeXYZ();
};
// foo_impl.h
#include "foo.h"
FOO_TEMPLATE
FOO_CLASS::Foo(){}
FOO_TEMPLATE
void FOO_CLASS::computeXYZ() { /* heavy code */ }
In questo modo, essenzialmente lavori allo stesso modo delle classi non template (ovviamente puoi fare la stessa cosa con le funzioni template).
EDIT: sulla parola chiave extern in c ++ 0x
Credo che la parola chiave extern in c ++ 0x aiuterà, ma non risolverà tutto magicamente!
Da questo articolo ,
Modelli esterni
Ogni modulo che crea un'istanza a il modello crea essenzialmente una copia di nel codice oggetto. Quindi è tutto al linker per disporre di tutto il file codice oggetto ridondante all'ultimo fase, rallentando così la critica ciclo di modifica-compilazione-collegamento che compone la giornata di un programmatore (o talvolta sogni ad occhi aperti). Per cortocircuitare questo garbage collection del codice oggetto, a numero di venditori di compilatori ha già implementato una parola chiave extern che può andare davanti ai modelli. Questo è un caso di standardizzazione codificare le pratiche industriali esistenti (gioco di parole previsto). In pratica, questo è implementato inviando un avviso al compilatore sostanzialmente per "non istanziare questo qui " ;:
extern template class std::vector;
C ++ 0x risolverà i tuoi problemi di tempo di compilazione con modelli esterni. Tuttavia, non conosco un modo automatico per fare ciò che chiedi.