2013-08-30 11 views
20

Tempo di pensare - Perché vuoi dividere il tuo file comunque?

Come suggerisce il titolo, il problema finale che ho sono errori di linker a più definizioni. Ho effettivamente risolto il problema, ma non ho risolto il problema nel modo corretto. Prima di iniziare voglio discutere i motivi per dividere un file di classe in più file. Ho provato a mettere qui tutti gli scenari possibili - se ne ho perso uno, per favore ricordamelo e posso apportare modifiche. Speriamo che i seguenti sono corretto:Separazione del codice di classe C++ in più file, quali sono le regole?

Motivo 1per risparmiare spazio:

Si dispone di un file contenente la dichiarazione di una classe con tutti i membri della classe. Puoi inserire #include protezioni attorno a questo file (o #pragma una volta) per garantire che non si verifichino conflitti se si include il file in due file di intestazione diversi che vengono quindi inclusi in un file sorgente. Compilate un file sorgente separato con l'implementazione di tutti i metodi dichiarati in questa classe, poiché carica molte righe di codice dal vostro file sorgente, il che pulisce un po 'le cose e introduce qualche ordine nel vostro programma.

Esempio: Come si può vedere, l'esempio di seguito potrebbe essere migliorato dividendo l'implementazione dei metodi di classe in un file diverso. (Un file cpp)

// my_class.hpp 
#pragma once 

class my_class 
{ 
public: 
    void my_function() 
    { 
     // LOTS OF CODE 
     // CONFUSING TO DEBUG 
     // LOTS OF CODE 
     // DISORGANIZED AND DISTRACTING 
     // LOTS OF CODE 
     // LOOKS HORRIBLE 
     // LOTS OF CODE 
     // VERY MESSY 
     // LOTS OF CODE 
    } 

    // MANY OTHER METHODS 
    // MEANS VERY LARGE FILE WITH LOTS OF LINES OF CODE 
} 

Motivo 2Per evitare errori multipli definizione linker:

Forse questo è il motivo principale per cui si dovrebbe dividere implementazione da dichiarazione. Nell'esempio precedente, è possibile spostare il corpo del metodo all'esterno della classe. Questo lo renderebbe molto più pulito e strutturato. Tuttavia, in base a questo question, l'esempio precedente ha impliciti parametri inline. Spostare l'implementazione dall'interno della classe all'esterno della classe, come nell'esempio seguente, causerà errori del linker, pertanto è necessario incorporare tutto o spostare le definizioni di funzione in un file .cpp.

Esempio: _L'esempio seguente causerà "errori di linker a più definizioni" se non si sposta la definizione di funzione in un file .cpp o si specifica la funzione come inline.

// my_class.hpp 
void my_class::my_function() 
{ 
    // ERROR! MULTIPLE DEFINITION OF my_class::my_function 
    // This error only occurs if you #include the file containing this code 
    // in two or more separate source (compiled, .cpp) files. 
} 

per risolvere il problema:

//my_class.cpp 
void my_class::my_function() 
{ 
    // Now in a .cpp file, so no multiple definition error 
} 

Oppure:

// my_class.hpp 
inline void my_class::my_function() 
{ 
    // Specified function as inline, so okay - note: back in header file! 
    // The very first example has an implicit `inline` specifier 
} 

Motivo 3Si vuole risparmiare spazio, ancora una volta, ma questa volta si sta lavorando con una classe template:

Se stiamo lavorando con classi template, non possiamo spostare l'implementazione in un file sorgente (file .cpp). Questo non è attualmente consentito da (presumo) o dai compilatori standard o attuali. A differenza del primo esempio di Motivo 2, sopra, siamo autorizzati a posizionare l'implementazione nel file di intestazione.In base a questo question, la ragione è che i metodi di classe template hanno anche implicito gli specificatori inline. È corretto? (Sembra avere un senso.) Ma nessuno sembrava sapere sulla domanda a cui ho appena fatto riferimento!

Quindi, i due esempi di seguito sono identici?

// some_header_file.hpp 
#pragma once 

// template class declaration goes here 
class some_class 
{ 
    // Some code 
}; 

// Example 1: NO INLINE SPECIFIER 
template<typename T> 
void some_class::class_method() 
{ 
    // Some code 
} 

// Example 2: INLINE specifier used 
template<typename T> 
inline void some_class::class_method() 
{ 
    // Some code 
} 

Se si dispone di un file di intestazione di classe template, che sta diventando enorme a causa di tutte le funzioni che avete, allora credo che si è permesso di spostare le definizioni di funzione a un altro file di intestazione (di solito un file .tpp?) e quindi #include file.tpp alla fine del file di intestazione contenente la dichiarazione della classe. NON devi includere questo file da nessun'altra parte, tuttavia, di conseguenza lo .tpp anziché lo .hpp.

Suppongo che si possa fare anche con i metodi in linea di una classe regolare? È permesso anche questo?

il tempo delle interrogazioni

così ho fatto alcune dichiarazioni di cui sopra, la maggior parte dei quali riguardano la strutturazione del file di origine. Penso che tutto ciò che ho detto sia corretto, perché ho fatto alcune ricerche di base e "ho scoperto alcune cose", ma questa è una domanda e quindi non lo so per certo.

Ciò a cui si riferisce è come organizzare il codice all'interno dei file. Penso di aver capito una struttura che funzionerà sempre.

Ecco quello che ho trovato. (Questo è "organizzazione file di codice di classe/struttura standard di Ed Uccello", se ti piace non so se sarà molto utile ancora, questo è il punto di chiedere..)

  • 1: Dichiarare la classe (modello o altro) in un file .hpp, inclusi tutti i metodi, le funzioni di amicizia e i dati.
  • 2: Nella parte inferiore del file .hpp, #include un file .tpp contenente l'attuazione di eventuali inline metodi. Creare il file .tpp e assicurarsi che tutti i metodi siano specificati in inline.
  • 3: Tutti gli altri membri (funzioni non-inline, funzioni amico e dati statici) dovrebbero essere definiti in un file .cpp, che #include s il file .hpp in alto per evitare errori come "classe ABC non è stato dichiarato ". Poiché tutto in questo file avrà un collegamento esterno, il programma si collegherà correttamente.

standard come questo esistono nell'industria? Lo standard mi ha insegnato a lavorare in tutti i casi?

+0

btw Ho scremato la tua domanda perché era troppo lunga. Informazioni sulla funzione del modello in linea. La parola chiave inline non è richiesta (hai ancora il permesso di inserirli nei file header) e non ha alcun effetto garantito. L'uso della parola chiave potrebbe rendere il compilatore più probabile che inline la funzione, ma nulla è certo. Queste funzioni del modello non sono implicitamente funzioni inline. La risposta a questa domanda è sbagliata, come discusso nei commenti della risposta. –

+0

@NeilKirk Ah grazie, la certezza sembrava esserci molto disaccordo a riguardo. – user3728501

+0

Ignora semplicemente mettere in linea lì e il problema non ti disturberà mai :) –

risposta

4

I tuoi tre punti sembrano giusti. Questo è il modo standard per fare le cose (anche se non ho visto l'estensione .tpp prima, di solito è .inl), anche se personalmente ho messo le funzioni inline nella parte inferiore dei file di intestazione piuttosto che in un file separato.

Ecco come sistemo i miei file. Ometto il forward dichiarare il file per classi semplici.

myclass-fwd.h

#pragma once 

namespace NS 
{ 
class MyClass; 
} 

MyClass.h

#pragma once 
#include "headers-needed-by-header" 
#include "myclass-fwd.h" 

namespace NS 
{ 
class MyClass 
{ 
    .. 
}; 
} 

MyClass.cpp

#include "headers-needed-by-source" 
#include "myclass.h" 

namespace 
    { 
    void LocalFunc(); 
} 

NS::MyClass::... 

Sostituire pragma con le guardie di intestazione in base alle preferenze ..

La ragione di questo approccio è quello di ridurre dipendenze dell'intestazione, che rallentano i tempi di compilazione in progetti di grandi dimensioni. Se non lo sapevi, puoi inoltrare una classe da usare come puntatore o riferimento. La dichiarazione completa è necessaria solo quando costruisci, crei o usi membri della classe.

Ciò significa che un'altra classe che utilizza la classe (utilizza parametri per puntatore/riferimento) deve solo includere l'intestazione fwd nella propria intestazione. L'intestazione completa viene quindi inclusa nel file sorgente della seconda classe. Ciò riduce notevolmente la quantità di spazzatura non necessaria che si ottiene quando si inserisce una grande intestazione, che tira in un'altra grande intestazione, che tira in un'altra ...

Il prossimo suggerimento è lo spazio dei nomi senza nome (a volte chiamato spazio dei nomi anonimo). Questo può apparire solo in un file sorgente ed è come uno spazio dei nomi nascosto visibile solo per quel file. È possibile inserire funzioni locali, classi ecc. Qui utilizzate solo dal file sorgente. Questo evita conflitti di nomi se crei qualcosa con lo stesso nome in due file diversi. (Due funzioni locali F, ad esempio, potrebbero dare errori di linker).

+1

Ah, sì, buoni consigli su namespace anonimo e file di intestazione di dichiarazione anticipata. Riesco a vedere un sacco di volte in cui la dichiarazione anticipata sarebbe particolarmente utile. Grazie! – user3728501

+0

@NeilKirk: Perché includi myclass-fwd.h in myclass.h? Avrei pensato che fosse ridondante. – quamrana

+0

@quamrana Nessun motivo, solo per essere coerenti. –

1

Il motivo principale per separare l'interfaccia dall'implementazione è che non è necessario ricompilare tutto il codice quando qualcosa nell'implementazione cambia; devi solo ricompilare i file sorgente che sono cambiati.

Per quanto riguarda "Dichiarare la classe (modello o altro)", un template non è un class. A template è un modello per creazione classi. Ancora più importante, tuttavia, è definire una classe o un modello in un'intestazione. La definizione della classe include le dichiarazioni delle sue funzioni membro e le funzioni membro non inine sono definite in uno o più file sorgente. Le funzioni membro inline e tutte le funzioni template devono essere definite nell'intestazione, in base a qualsiasi combinazione di definizioni dirette e direttive #include che preferisci.

+1

"Come per" Dichiara la classe (modello o altro) ", un modello non è una classe.Un modello è un modello per la creazione di classi." - Sì, lo so – user3728501

0

Esistono standard simili in questo settore?

Sì. Quindi, ancora una volta, gli standard di codifica che sono piuttosto diversi da quelli che hai espresso possono essere trovati anche nell'industria. Dopotutto, stai parlando di standard di codifica e gli standard di codifica variano da buono a cattivo a cattivo.

La norma mi è venuta in mente in tutti i casi?

Assolutamente no. Ad esempio,

template <typename T> class Foo { 
public: 
    void some_method (T& arg); 
... 
}; 

Qui, la definizione di modello di classe Foo non sa nulla di che T. parametro di template Che cosa succede se, per un certo modello di classe, le definizioni dei metodi variano a seconda dei parametri del modello? La tua regola n. 2 non funziona qui.

Un altro esempio: cosa succede se il file sorgente corrispondente è enorme, lungo mille o più righe? A volte ha senso fornire l'implementazione in più file sorgente. Alcuni standard arrivano all'estremo di dettare una funzione per file (opinione personale: Yech!).

All'estremo opposto di un file sorgente lungo più di mille righe è una classe che non ha file di origine. L'intera implementazione è nell'intestazione. C'è molto da dire per le implementazioni di sola intestazione. Se non altro, semplifica, a volte in modo significativo, il problema del collegamento.

+0

Intestazione solo come in: Tutti i metodi dichiarati e definiti all'interno delle parentesi di classe ('{' e '}')? Prendo il tuo punto di vista sulla prevenzione degli errori di linker, ma ogni membro che è più complesso di una riga di codice, per esempio, è più complesso di un semplice '{return m_time; } ', produce codice orribile. – user3728501

+0

Solo intestazione, come in tutti i metodi sono definiti all'interno di un file di intestazione. Indipendentemente dal fatto che sia all'interno della classe, come una definizione fuori linea al di fuori della classe, ma sempre nello stesso file di intestazione, o qualche altro file incluso da quella prima intestazione è una domanda diversa. Il punto chiave: non esiste un file sorgente che deve essere compilato separatamente e collegato. Un sacco di Boost, ad esempio, è un'implementazione di solo intestazione. –

+0

Per qualcosa di più complesso di '{return something; } 'non appartiene alla definizione della classe: * Questa è la tua opinione *. Altre persone hanno altre opinioni. Personalmente non mi dispiace vedere un corto (ma non un solo liner) all'interno della definizione della classe. La mia regola personale: se inserisco una definizione di funzione all'interno della classe se penso che farlo aiuterà gli altri a capire quella classe, questa è la mia opinione. Diffida di porre problemi di preferenza personale all'interno di uno standard di codifica. Farà in modo che le persone ignorino i tuoi standard. –

Problemi correlati