Quali sono i vantaggi di una libreria di sola intestazione e perché dovresti scriverla in questo modo opporsi a mettere l'implementazione in un file separato?
Quali sono i vantaggi di una libreria di sola intestazione e perché dovresti scriverla in questo modo opporsi a mettere l'implementazione in un file separato?
Risposte:
Ci sono situazioni in cui una libreria di sola intestazione è l'unica opzione, ad esempio quando si ha a che fare con i modelli.
Avere una libreria di sola intestazione significa anche che non devi preoccuparti delle diverse piattaforme in cui la libreria potrebbe essere utilizzata. Quando si separa l'implementazione, di solito lo si fa per nascondere i dettagli dell'implementazione e distribuire la libreria come una combinazione di intestazioni e librerie ( lib
, dll
o .so
file). Questi ovviamente devono essere compilati per tutti i diversi sistemi operativi / versioni che offri supporto.
Potresti anche distribuire i file di implementazione, ma ciò significherebbe un passaggio extra per l'utente: compilare la tua libreria prima di usarla.
Ovviamente questo si applica caso per caso . Ad esempio, le librerie di sola intestazione a volte aumentanodimensione del codice e tempi di compilazione.
Vantaggi della libreria di sola intestazione:
Svantaggi di una libreria di sola intestazione:
File oggetto più grandi. Ogni metodo inline dalla libreria che viene utilizzato in qualche file di origine riceverà anche un simbolo debole, una definizione fuori linea nel file oggetto compilato per quel file di origine. Ciò rallenta il compilatore e rallenta anche il linker. Il compilatore deve generare tutto quel rigonfiamento, quindi il linker deve filtrarlo.
Compilazione più lunga. Oltre al problema di ingombro menzionato sopra, la compilazione richiederà più tempo perché le intestazioni sono intrinsecamente più grandi con una libreria di sola intestazione rispetto a una libreria compilata. Quelle grandi intestazioni dovranno essere analizzate per ogni file sorgente che utilizza la libreria. Un altro fattore è che quei file di intestazione in una libreria di sola #include
intestazione devono avere le intestazioni necessarie per le definizioni inline così come le intestazioni che sarebbero necessarie se la libreria fosse stata costruita come libreria compilata.
Compilazione più intricata. Ottieni molte più dipendenze con una libreria di sola intestazione a causa di quei messaggi aggiuntivi #include
necessari con una libreria di sola intestazione. Cambia l'implementazione di alcune funzioni chiave nella libreria e potresti dover ricompilare l'intero progetto. Apporta la modifica nel file di origine per una libreria compilata e tutto ciò che devi fare è ricompilare quel file di origine di una libreria, aggiornare la libreria compilata con quel nuovo file .o e ricollegare l'applicazione.
Più difficile da leggere per l'uomo. Anche con la migliore documentazione, gli utenti di una libreria spesso devono ricorrere alla lettura delle intestazioni per la libreria. Le intestazioni in una libreria di sola intestazione sono piene di dettagli di implementazione che ostacolano la comprensione dell'interfaccia. Con una libreria compilata, tutto ciò che vedi è l'interfaccia e un breve commento su ciò che fa l'implementazione, e di solito è tutto ciò che desideri. È davvero tutto ciò che dovresti desiderare. Non dovresti dover conoscere i dettagli di implementazione per sapere come utilizzare la libreria.
detail
.
So che questo è un vecchio thread, ma nessuno ha menzionato interfacce ABI o problemi specifici del compilatore. Quindi ho pensato di farlo.
Questo è fondamentalmente basato sul concetto di te che scrivi una libreria con un'intestazione da distribuire alle persone o riutilizzi te stesso anziché avere tutto in un'intestazione. Se stai pensando di riutilizzare un'intestazione e dei file sorgente e di ricompilarli in ogni progetto, questo non si applica realmente.
Fondamentalmente se si compila il codice C ++ e si crea una libreria con un compilatore, l'utente tenta di utilizzare quella libreria con un compilatore diverso o una versione diversa dello stesso compilatore, è possibile che si verifichino errori del linker o strani comportamenti di runtime a causa dell'incompatibilità binaria.
Ad esempio, i fornitori di compilatori spesso cambiano la loro implementazione dell'STL tra le versioni. Se hai una funzione in una libreria che accetta uno std :: vector, allora si aspetta che i byte in quella classe siano disposti nel modo in cui erano disposti quando la libreria è stata compilata. Se, in una nuova versione del compilatore, il fornitore ha apportato miglioramenti all'efficienza a std :: vector, il codice dell'utente vede la nuova classe che potrebbe avere una struttura diversa e passa quella nuova struttura alla tua libreria. Tutto va in discesa da lì ... Questo è il motivo per cui si consiglia di non passare oggetti STL oltre i confini della libreria. Lo stesso vale per i tipi C Run-Time (CRT).
Mentre parli del CRT, la tua libreria e il codice sorgente dell'utente generalmente devono essere collegati allo stesso CRT. Con Visual Studio, se crei la tua libreria utilizzando il CRT multithread, ma l'utente si collega al CRT di debug multithread, avrai problemi di collegamento perché la tua libreria potrebbe non trovare i simboli di cui ha bisogno. Non ricordo quale funzione fosse, ma per Visual Studio 2015 Microsoft ha creato una funzione CRT inline. All'improvviso non era nell'intestazione della libreria CRT, quindi le librerie che si aspettavano di trovarla al momento del collegamento non potevano più farlo e questo generava errori di collegamento. Il risultato è stato che queste librerie dovevano essere ricompilate con Visual Studio 2015.
È inoltre possibile ottenere errori di collegamento o comportamenti strani se si utilizza l'API di Windows ma si crea con impostazioni Unicode diverse per l'utente della libreria. Questo perché l'API di Windows ha funzioni che utilizzano stringhe Unicode o ASCII e macro / definisce che utilizzano automagicamente i tipi corretti in base alle impostazioni Unicode del progetto. Se si passa una stringa attraverso il limite della libreria che è del tipo sbagliato, le cose si interrompono in fase di esecuzione. Oppure potresti scoprire che il programma non si collega in primo luogo.
Queste cose sono valide anche per il passaggio di oggetti / tipi attraverso i confini della libreria da altre librerie di terze parti (ad esempio un vettore Eigen o una matrice GSL). Se la libreria di terze parti cambia la propria intestazione tra te che compili la tua libreria e il tuo utente che compila il loro codice, le cose si interromperanno.
Fondamentalmente per essere sicuri le uniche cose che puoi passare attraverso i confini della libreria sono i tipi e Plain Old Data (POD). Idealmente qualsiasi POD dovrebbe essere in strutture definite nelle proprie intestazioni e non fare affidamento su intestazioni di terze parti.
Se fornisci una libreria di sola intestazione, tutto il codice viene compilato con le stesse impostazioni del compilatore e contro le stesse intestazioni, quindi molti di questi problemi scompaiono (a condizione che la versione delle terze librerie in parte utilizzate da te e dai tuoi utenti sia compatibile con l'API).
Tuttavia ci sono aspetti negativi che sono stati menzionati sopra, come l'aumento del tempo di compilazione. Inoltre potresti gestire un'attività, quindi potresti non voler consegnare tutti i dettagli di implementazione del codice sorgente a tutti i tuoi utenti nel caso in cui uno di loro lo rubi.
Il "vantaggio" principale è che richiede di fornire il codice sorgente, quindi ti ritroverai con rapporti di errore sulle macchine e con compilatori di cui non hai mai sentito parlare. Quando la libreria è interamente di modelli, non hai molta scelta, ma quando hai la possibilità, solo l'intestazione di solito è una scelta ingegneristica scadente. (D'altra parte, ovviamente, l'intestazione significa solo che non è necessario documentare alcuna procedura di integrazione.)
L'inlining può essere eseguito tramite Link Time Optimization (LTO)
Vorrei evidenziarlo poiché diminuisce il valore di uno dei due principali vantaggi delle librerie di sola intestazione: "hai bisogno di definizioni su un'intestazione per inline".
Un esempio concreto minimo di ciò è mostrato in: Ottimizzazione del tempo di collegamento e inline
Quindi basta passare un flag e l'inlining può essere eseguito tra i file oggetto senza alcun lavoro di refactoring, non è più necessario mantenere le definizioni nelle intestazioni per questo.
Tuttavia, anche LTO potrebbe avere i suoi svantaggi: c'è un motivo per non utilizzare l'ottimizzazione del tempo di collegamento (LTO)?