Vantaggi delle librerie di sola intestazione


98

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?


Principalmente modelli, ma renderà anche un po 'più facile da distribuire e utilizzare.
BoBTFish

4
Vorrei aggiungere gli
aspetti

Quali aspetti negativi ci sono che non sono già stati menzionati?
NebulaFox

7
@moooeeeep: per gli aspetti negativi, potresti leggere il paragrafo "Stop inlining code" nella pagina web C ++ di Chromium Projects su cosa fare e cosa non fare .
Mr C64

Risposte:


57

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, dllo .sofile). 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.


6
"Avere una libreria di sola intestazione significa anche che non devi preoccuparti delle diverse piattaforme in cui la libreria potrebbe essere utilizzata": solo se non devi mantenere la libreria. Altrimenti, è un incubo, con segnalazioni di bug che non puoi riprodurre o testare sul materiale che hai.
James Kanze

1
Ho appena posto una domanda simile sui vantaggi in termini di prestazioni della sola intestazione. Come puoi vedere, non c'è differenza nella dimensione del codice. Tuttavia, l'implementazione della sola intestazione di esempio è stata eseguita più lentamente del 7%. stackoverflow.com/questions/12290639/...
Homer6

@ Homer6 grazie per avermi contattato. Non l'ho mai misurato realmente.
Luchian Grigore

1
@LuchianGrigore Non sono riuscito a trovare nessun altro che avesse nessuno dei due. Ecco perché ci è voluto un po 'per rispondere. Ci sono così tanti commenti speculativi "aumenta la dimensione del codice" e "consumo di memoria". Finalmente ho un'istantanea delle differenze, anche se è solo un esempio.
Homer6

@ Homer6. Perché non aumenterebbe la dimensione del codice? Supponendo che tu crei più librerie che usano solo l'intestazione lib e quindi la tua app usa tutte quelle librerie dovresti avere più copie invece di collegarle a una singola libreria condivisa.
pooya13

60

Vantaggi della libreria di sola intestazione:

  • Semplifica il processo di compilazione. Non è necessario creare la libreria e non è necessario specificare la libreria compilata durante la fase di collegamento della compilazione. Se hai una libreria compilata, probabilmente vorrai crearne più versioni: una compilata con il debug abilitato, un'altra con l'ottimizzazione abilitata e forse un'altra ancora priva di simboli. E forse anche di più per un sistema multipiattaforma.

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 #includeintestazione 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 #includenecessari 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.


21
L'ultimo punto non ha davvero senso. Qualsiasi documentazione ragionevole includerà la dichiarazione della funzione, i parametri, i valori restituiti, ecc. E tutti i commenti associati. Se devi fare riferimento al file di intestazione, la documentazione non è riuscita.
Thomas

6
@Thomas - Anche con le migliori biblioteche professionali, mi ritrovo spesso a dover ricorrere alla lettura dell'intestazione "fine". In effetti, se la cosiddetta documentazione "fine" viene estratta dal codice più il commento, in genere mi piace leggere le intestazioni. Il codice più i commenti mi dicono più della documentazione generata automaticamente.
David Hammen

2
L'ultimo punto non è valido. Le intestazioni sono già riempite con i dettagli di implementazione nei membri privati, quindi non è che il file cpp nasconda tutti i dettagli di implementazione. Inoltre, linguaggi come C # sono essenzialmente "solo intestazione" in base alla progettazione e l'IDE si occupa di oscurare i dettagli ("piegarli" verso il basso)
Mark Lakata

2
@Tomas: D'accordo, l'ultimo punto è completamente falso. Puoi facilmente mantenere l'interfaccia e l'implementazione altrettanto separate con le librerie di sola intestazione; hai semplicemente l'intestazione dell'interfaccia #include i dettagli di implementazione. Questo è il motivo per cui le librerie Boost in genere includono una sottodirectory (e uno spazio dei nomi) chiamata detail.
Nemo

4
@ Thomas: non sono d'accordo. Il file di intestazione è generalmente il primo posto in cui vado per la documentazione. Se l'intestazione è scritta bene, spesso non è necessaria la documentazione esterna.
Joel Cornett

14

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.


8

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.)


0

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)?

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.