Differenza tra l'implementazione di una classe all'interno di un file .h o in un file .cpp

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

  •  05-07-2019
  •  | 
  •  

Domanda

Mi chiedevo quali sono le differenze tra dichiarare e implementare una classe esclusivamente in un file di intestazione, rispetto all'approccio normale in cui si digita la classe nell'intestazione e si implementa in un file .cpp efficace.

Per spiegare meglio di cosa sto parlando intendo le differenze tra l'approccio normale:

// File class.h
class MyClass 
{
private:
  //attributes
  public:
  void method1(...);
  void method2(...);
  ...
};

//file class.cpp
#include "class.h"

void MyClass::method1(...) 
{
  //implementation
}

void MyClass::method2(...) 
{
  //implementation
}

e un approccio just-header :

// File class.h
class MyClass 
{
private:
  //attributes
public:
  void method1(...) 
  {
      //implementation
  }

  void method2(...) 
  {
    //implementation
  }

  ...
};

Posso ottenere la differenza principale: nel secondo caso il codice è incluso in ogni altro file che ne ha bisogno generando più istanze delle stesse implementazioni, quindi una ridondanza implicita; mentre nel primo caso il codice viene compilato da solo e quindi ogni chiamata riferita all'oggetto di MyClass è collegata all'implementazione in class.cpp .

Ma ci sono altre differenze? È più conveniente utilizzare un approccio anziché un altro a seconda della situazione? Ho anche letto da qualche parte che definire il corpo di un metodo direttamente in un file di intestazione è una richiesta implicita al compilatore per incorporare quel metodo, è vero?

È stato utile?

Soluzione

La principale differenza pratica è che se le definizioni delle funzioni membro si trovano nel corpo dell'intestazione, ovviamente vengono compilate una volta per ogni unità di traduzione che include quell'intestazione. Quando il tuo progetto contiene alcune centinaia o migliaia di file sorgente e la classe in questione è ampiamente utilizzata, ciò potrebbe significare molte ripetizioni. Anche se ogni classe viene utilizzata solo da altre 2 o 3, maggiore è il codice nell'intestazione, maggiore è il lavoro da svolgere.

Se le definizioni delle funzioni membro si trovano in un'unità di traduzione (file .cpp) propria, vengono compilate una volta e solo le dichiarazioni delle funzioni vengono compilate più volte.

È vero che le funzioni membro definite (non solo dichiarate) nella definizione della classe sono implicitamente inline . Ma inline non significa ciò che la gente potrebbe ragionevolmente supporre che significhi. inline afferma che è legale che più definizioni della funzione appaiano in unità di traduzione diverse e che successivamente siano collegate tra loro. Ciò è necessario se la classe si trova in un file di intestazione che verranno utilizzati diversi file di origine, quindi la lingua cerca di essere utile.

inline è anche un suggerimento per il compilatore che la funzione potrebbe essere utilmente incorporata, ma nonostante il nome, è facoltativo. Quanto più sofisticato è il tuo compilatore, tanto migliore è in grado di prendere le proprie decisioni in merito all'allineamento e minore è la necessità di suggerimenti. Più importante dell'attuale tag inline è se la funzione è disponibile per il compilatore. Se la funzione è definita in una diversa unità di traduzione, allora non è disponibile quando viene compilata la chiamata, e quindi se qualcosa sta per incorporare la chiamata, dovrà essere il linker, non il compilatore.

Potresti essere in grado di vedere meglio le differenze considerando un terzo modo possibile di farlo:

// File class.h
class MyClass
{
    private:
        //attributes
    public:
       void method1(...);
       void method2(...);
       ...
};

inline void MyClass::method1(...)
{
     //implementation
}

inline void MyClass::method2(...)
{
     //implementation
}

Ora che l'inline implicito è fuori mano, rimangono alcune differenze tra questa "intestazione" approccio e l'intestazione " più sorgente " approccio. Il modo in cui dividi il codice tra unità di traduzione ha conseguenze su ciò che accade man mano che viene creato.

Altri suggerimenti

Sì, il compilatore tenterà di incorporare un metodo dichiarato direttamente nel file di intestazione come:

class A
{
 public:
   void method()
   {
   }
};

Posso pensare ai seguenti vantaggi nel separare l'implementazione nei file di intestazione:

  1. Non avrai codice gonfiato a causa dello stesso codice che viene incluso in più unità di traduzione
  2. Il tempo di compilazione si ridurrà drasticamente. Ricordalo per qualsiasi modifica nel file di intestazione il compilatore deve compilare tutti gli altri file che direttamente o indirettamente includilo Immagino che sarà molto frustrante per chiunque di costruire il tutto di nuovo binario solo per l'aggiunta di a spazio nel file di intestazione.

Qualsiasi modifica a un'intestazione che include l'implementazione costringerà tutte le altre classi che includono quell'intestazione a ricompilarsi e ricollegarsi.

Poiché le intestazioni cambiano meno frequentemente delle implementazioni, inserendo l'implementazione in un file separato, è possibile risparmiare un notevole tempo di compilazione.

Come hanno già indicato alcune altre risposte, sì, la definizione di un metodo all'interno del blocco class di un file renderà in linea il compilatore.

Sì, la definizione dei metodi all'interno della definizione della classe equivale a dichiararli inline . Non c'è altra differenza. Non è utile definire tutto nel file di intestazione.

Qualcosa del genere si vede di solito in C ++ con le classi template, poiché anche le definizioni dei membri template devono essere incluse nel file header (a causa del fatto che la maggior parte dei compilatori non supporta export ). Ma con le normali classi non template non ha senso farlo, a meno che tu non voglia davvero dichiarare i tuoi metodi come inline .

Per me, la differenza principale è che un file di intestazione è come una "interfaccia" per la classe, dicendo ai clienti di quella classe quali sono i suoi metodi pubblici (le operazioni che supporta), senza che i clienti si preoccupino dell'implementazione specifica di quelli. In un certo senso, è un modo per incapsulare i suoi clienti dalle modifiche di implementazione, perché cambia solo il file cpp e quindi il tempo di compilazione è molto inferiore.

Una volta in passato ho creato un modulo che proteggeva dalle differenze nelle varie distribuzioni CORBA e ci si aspettava che funzionasse uniformemente su varie combinazioni lib OS / compilatore / CORBA. Renderlo implementato in un file di intestazione ha reso più facile aggiungerlo a un progetto con una semplice inclusione. La stessa tecnica garantiva che il codice fosse ricompilato contemporaneamente quando il codice che lo chiamava richiedeva la ricompilazione, cioè quando veniva compilato con una libreria diversa o su un sistema operativo diverso.

Quindi il mio punto è che se hai una libreria piuttosto piccola che dovrebbe essere riutilizzabile e ricompilabile in vari progetti, rendendola un'intestazione offre vantaggi nell'integrazione con alcuni altri progetti rispetto all'aggiunta di file extra al progetto principale o alla ricompilazione un file lib / obj esterno.

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