Modi per organizzare l'interfaccia e l'implementazione in C ++


12

Ho visto che ci sono diversi paradigmi in C ++ riguardo a ciò che va nel file header e cosa al file cpp. AFAIK, la maggior parte delle persone, in particolare quelle di origine C, fanno:

foo.h

 class foo {
 private:
     int mem;
     int bar();
 public:
     foo();
     foo(const foo&);
     foo& operator=(foo);
     ~foo();
 }

foo.cpp

 #include foo.h
 foo::bar() { return mem; }
 foo::foo() { mem = 42; }
 foo::foo(const foo& f) { mem = f.mem; }
 foo::operator=(foo f) { mem = f.mem; }
 foo::~foo() {}
 int main(int argc, char *argv[]) { foo f; }

Tuttavia, i miei docenti di solito insegnano C ++ ai principianti in questo modo:

foo.h

 class foo {
 private:
     int mem;
     int bar() { return mem; }
 public:
     foo() { mem = 42; }
     foo(const foo& f) { mem = f.mem; }
     foo& operator=(foo f) { mem = f.mem; }
     ~foo() {}
 }

foo.cpp

 #include foo.h
 int main(int argc, char* argv[]) { foo f; }
 // other global helper functions, DLL exports, and whatnot

Originario di Java, mi sono sempre attenuto a questo secondo modo per diversi motivi, come quello di cambiare qualcosa in un posto solo se cambiano i nomi dell'interfaccia o del metodo, che mi piace la diversa rientranza delle cose nelle classi quando guarda la loro implementazione e che trovo nomi più leggibili foorispetto a foo::foo.

Voglio raccogliere pro e contro in entrambi i modi. Forse ci sono ancora altri modi?

Uno svantaggio della mia strada è ovviamente la necessità di occasionali dichiarazioni a termine.


2
foo.cppora non ha nulla a che fare con la tua fooclasse e dovrebbe essere lasciato vuoto (forse #includeper rendere felice il tuo sistema di compilazione).
Benjamin Bannier,

2
I tuoi docenti sono pazzi.
Razze di leggerezza in orbita,

Risposte:


16

Mentre la seconda versione è più facile da scrivere, sta mescolando l'interfaccia con l'implementazione.

I file di origine che includono i file di intestazione devono essere ricompilati ogni volta che vengono modificati i file di intestazione. Nella prima versione è necessario modificare il file di intestazione solo se è necessario modificare l'interfaccia. Nella seconda versione è necessario modificare il file di intestazione se è necessario modificare l'interfaccia o l'implementazione.

Oltre a ciò non dovresti esporre i dettagli dell'implementazione , otterrai una ricompilazione inutile con la seconda versione.


1
+1 Il mio profiler non utilizza il codice inserito nei file di intestazione, anche questo è un motivo prezioso.
Eugene,

Se vedi la mia risposta a questa domanda programmers.stackexchange.com/questions/4573/… vedrai come ciò dipende molto dalla semantica della classe, cioè da cosa la utilizzerà (in particolare se è una parte esposta di la tua interfaccia utente e quante altre classi nel tuo sistema la usano direttamente).
CashCow,

3

L'ho fatto la seconda volta nel '93 -95. Ci sono voluti alcuni minuti per ricompilare una piccola app con 5-10 funzioni / file (sullo stesso 486 PC .. e no, non sapevo nemmeno delle lezioni, avevo solo 14-15 anni e non c'era internet ) .

Quindi, ciò che insegni ai principianti e ciò che usi professionalmente sono tecniche molto diverse, specialmente in C ++.

Penso che il confronto tra C ++ e un'auto da F1 sia appropriato. Non metti i principianti in un'auto di F1 (che non si avvia nemmeno se non preriscaldi il motore a 80-95 gradi Celsius).

Non insegnare il C ++ come prima lingua. Dovresti avere abbastanza esperienza per sapere perché l'opzione 2 è peggio dell'opzione 1 in generale, sapere un po 'cosa significa compilazione / collegamento statico e quindi capire perché C ++ la preferisce nel primo modo.


Questa risposta sarebbe ancora migliore se avessi elaborato un po 'di compilazione / collegamento statici (non lo sapevo allora!)
Felix Dombek,

2

Il secondo metodo è quello che definirei una classe totalmente integrata. Stai scrivendo una definizione di classe ma tutto il tuo codice che lo utilizza incorporerà semplicemente il codice.

Sì, il compilatore decide quando incorporare e quando non ... In questo caso stai aiutando il compilatore a prendere una decisione, e potenzialmente finirai per generare effettivamente meno codice e potenzialmente più veloce.

È probabile che questo vantaggio superi il fatto che se si modifica l'implementazione di una funzione è necessario ricostruire tutta l'origine che la utilizza. Nella natura leggera della classe non modificherai l'implementazione. Se aggiungi un nuovo metodo, dovresti comunque modificare l'intestazione.

Tuttavia, man mano che la tua classe diventa più complessa, anche aggiungendo un loop, il vantaggio di farlo in questo modo diminuisce.

Ha ancora i suoi vantaggi, in particolare:

  • Se si tratta di un codice di "funzionalità comune", puoi semplicemente includere l'intestazione e utilizzarla da più progetti senza dover collegarti a una libreria che ne contiene l'origine.

Il rovescio della medaglia di inline diventa un problema quando significa che devi inserire specifiche di implementazione nella tua intestazione, cioè devi iniziare a includere intestazioni extra.

Si noti che i modelli sono un caso speciale in quanto è necessario includere i dettagli di implementazione. Potresti oscurarlo in un altro file ma deve essere lì. (Esiste un'eccezione a tale regola con le istanze, ma in generale si incorporano i modelli).


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.