È una cattiva pratica usare un compilatore C ++ solo per sovraccarico di funzioni?
Punto di vista di IMHO, sì, e dovrò diventare schizofrenico per rispondere a questa domanda poiché amo entrambe le lingue ma non ha nulla a che fare con l'efficienza, ma più come la sicurezza e l'uso idiomatico delle lingue.
Lato C.
Da un punto di vista C, trovo così dispendioso che il tuo codice richieda C ++ solo per usare il sovraccarico della funzione. A meno che non lo si stia utilizzando per il polimorfismo statico con modelli C ++, è uno zucchero sintattico così banale ottenuto in cambio del passaggio a un linguaggio completamente diverso. Inoltre, se mai vuoi esportare le tue funzioni in un dylib (può o meno essere un problema pratico), non puoi più farlo in modo molto pratico per un consumo diffuso con tutti i simboli alterati dal nome.
Lato C ++
Da un punto di vista C ++, non dovresti usare C ++ come C con sovraccarico di funzioni. Questo non è dogmatismo stilistico, ma legato all'uso pratico del C ++ quotidiano.
Il tuo normale tipo di codice C è solo ragionevolmente sano e "sicuro" da scrivere se stai lavorando contro il sistema di tipo C che vieta cose come copiare i dottori structs
. Una volta che lavori nel sistema di tipo molto più ricco di C ++, le funzioni quotidiane che hanno un valore enorme come memset
e memcpy
non diventano funzioni su cui ti devi appoggiare tutto il tempo. Invece, sono funzioni che generalmente si vogliono evitare come la peste, dal momento che con i tipi C ++ non si dovrebbero trattarli come bit e byte grezzi da copiare, mescolare e liberare. Anche se il tuo codice utilizza solo cose come memset
su primitive e POD UDT al momento, nel momento in cui qualcuno aggiunge un ctor a qualsiasi UDT che usi (incluso solo l'aggiunta di un membro che richiede uno, comestd::unique_ptr
membro) contro tali funzioni o una funzione virtuale o qualcosa del genere, rende tutta la normale codifica in stile C sensibile a comportamenti indefiniti. Prendilo dallo stesso Herb Sutter:
memcpy
e memcmp
violare il sistema dei tipi. Usare memcpy
per copiare oggetti è come fare soldi usando una fotocopiatrice. Usare memcmp
per confrontare gli oggetti è come confrontare i leopardi contando i loro punti. Gli strumenti e i metodi potrebbero sembrare in grado di fare il lavoro, ma sono troppo grossolani per farlo in modo accettabile. Gli oggetti C ++ riguardano il nascondere le informazioni (probabilmente il principio più redditizio nell'ingegneria del software; vedi Articolo 11): gli oggetti nascondono i dati (vedi Articolo 41) e escogitano astrazioni precise per la copia di tali dati attraverso costruttori e operatori di assegnazione (vedi Articoli da 52 a 55) . La demolizione di tutto ciò comporta memcpy
una grave violazione del nascondere le informazioni e spesso porta a perdite di memoria e risorse (nella migliore delle ipotesi), arresti anomali (peggiore) o comportamento indefinito (peggiore) - Standard di codifica C ++.
Così tanti sviluppatori C non sarebbero d'accordo con questo e giustamente, poiché la filosofia si applica solo se si sta scrivendo codice in C ++. Molto probabilmente sta scrivendo codice molto problematico se si utilizzano funzioni come memcpy
tutti i tempi nel codice che costruisce come C ++ , ma è perfettamente bene se lo si fa in C . Le due lingue sono molto diverse a questo proposito a causa delle differenze nel sistema dei tipi. È molto allettante guardare il sottoinsieme di caratteristiche che questi due hanno in comune e ritenere che uno possa essere usato come l'altro, specialmente sul lato C ++, ma il codice C + (o codice C--) è generalmente molto più problematico di entrambi C e Codice C ++.
Allo stesso modo non dovresti usare, diciamo, malloc
in un contesto in stile C (che non implica EH) se può chiamare direttamente qualsiasi funzione C ++ che può essere lanciata, da allora hai un punto di uscita implicito nella tua funzione come risultato del eccezione che non è possibile rilevare efficacemente la scrittura di codice in stile C, prima di essere in grado di free
tale memoria. Quindi, ogni volta che si dispone di un file che si basa, come C ++ con .cpp
un'estensione o qualsiasi altra cosa e lo fa tutti questi tipi di cose come malloc
, memcpy
, memset
,qsort
, ecc., quindi sta chiedendo ulteriori problemi, se non già, a meno che non siano i dettagli di implementazione di una classe che funziona solo con tipi primitivi, a quel punto deve ancora fare la gestione delle eccezioni per essere sicuro delle eccezioni. Se si scrive codice C ++ si desidera invece fare affidamento in generale sulla RAII e utilizzare le cose come vector
, unique_ptr
, shared_ptr
, ecc, e di evitare tutto normale codifica C-stile quando possibile.
Il motivo per cui puoi giocare con le lamette nei tipi di dati a raggi X e C e giocare con i loro bit e byte senza essere incline a causare danni collaterali in una squadra (anche se puoi comunque farti del male in entrambi i modi) non è a causa di ciò che C i tipi possono fare, ma a causa di ciò che non saranno mai in grado di fare. Nel momento in cui estendi il sistema di tipi C per includere funzionalità C ++ come ctors, dtors e vtables, insieme alla gestione delle eccezioni, tutto il codice C idiomatico sarebbe reso molto, molto più pericoloso di quello che è attualmente, e vedrai un nuovo tipo di l'evoluzione della filosofia e della mentalità che incoraggerà uno stile completamente diverso di codifica, come vedete in C ++, che ora considera anche l'uso di una pratica scorretta del puntatore non elaborato per una classe che gestisce la memoria al contrario, diciamo, di una risorsa conforme alla RAII come unique_ptr
. Quella mentalità non si è evoluta da un assoluto senso di sicurezza. Si è evoluto da ciò che il C ++ ha bisogno in particolare di essere al sicuro da funzionalità come la gestione delle eccezioni, dato ciò che consente semplicemente attraverso il suo sistema di tipi.
Eccezione-sicurezza
Ancora una volta, nel momento in cui ti trovi in C ++, le persone si aspetteranno che il tuo codice sia sicuro dalle eccezioni. Le persone potrebbero conservare il tuo codice in futuro, dato che è già scritto e compilato in C ++ e semplicemente utilizzarlo std::vector, dynamic_cast, unique_ptr, shared_ptr
, ecc. Nel codice chiamato direttamente o indirettamente dal tuo codice, ritenendolo innocuo poiché il tuo codice è già "presumibilmente" C ++ codice. A quel punto dobbiamo affrontare la possibilità che le cose lancino, e poi quando prendi il codice C perfettamente bene e adorabile come questo:
int some_func(int n, ...)
{
int* x = calloc(n, sizeof(int));
if (x)
{
f(n, x); // some function which, now being a C++ function, may
// throw today or in the future.
...
free(x);
return success;
}
return fail;
}
... ora è rotto. Deve essere riscritto per essere sicuro delle eccezioni:
int some_func(int n, ...)
{
int* x = calloc(n, sizeof(int));
if (x)
{
try
{
f(n, x); // some function which, now being a C++ function, may
// throw today or in the future (maybe someone used
// std::vector inside of it).
}
catch (...)
{
free(x);
throw;
}
...
free(x);
return success;
}
return fail;
}
Schifoso! Ecco perché la maggior parte degli sviluppatori C ++ lo richiederebbe invece:
void some_func(int n, ...)
{
vector<int> x(n);
f(x); // some function which, now being a C++ function, may throw today
// or in the future.
}
Quanto sopra è un codice sicuro per le eccezioni conforme a RAII del tipo che gli sviluppatori C ++ generalmente approverebbero poiché la funzione non perde alcuna linea di codice che provoca un'uscita implicita come risultato di a throw
.
Scegli una lingua
Dovresti abbracciare il sistema di tipo C ++ e la filosofia con RAII, eccezioni, modelli, OOP, ecc. O abbracciare C che ruota in gran parte attorno a bit e byte grezzi. Non dovresti formare un matrimonio empio tra queste due lingue e invece separarle in lingue distinte per essere trattate in modo molto diverso invece di offuscarle insieme.
Queste lingue vogliono sposarti. In genere devi sceglierne uno invece di uscire e scherzare con entrambi. Oppure puoi essere un poligamo come me e sposare entrambi, ma devi cambiare completamente il tuo pensiero quando trascorri del tempo l'uno con l'altro e tenerli ben separati l'uno dall'altro in modo che non si combattano.
Dimensione binaria
Solo per curiosità ho provato a prendere l'implementazione della mia lista gratuita e il benchmark proprio ora e portarlo su C ++ da quando mi sono davvero incuriosito:
[...] non so come sarebbe C perché non sto usando il compilatore C.
... e volevo sapere se le dimensioni binarie si gonfiassero semplicemente costruendo come C ++. Mi ha richiesto di cospargere cast espliciti in tutto il luogo che era fugace (una ragione per cui mi piace davvero scrivere cose di basso livello come allocatori e strutture di dati in C meglio) ma ci è voluto solo un minuto.
Si trattava solo di confrontare una build di rilascio MSVC a 64 bit per una semplice app console e con il codice che non utilizzava alcuna funzionalità C ++, nemmeno il sovraccarico dell'operatore: solo la differenza tra la creazione con C e l'utilizzo, diciamo, <cstdlib>
invece di <stdlib.h>
e cose del genere, ma sono stato sorpreso di scoprire che ha fatto la differenza zero rispetto alla dimensione binaria!
Il binario era 9,728
byte quando era costruito in C, e allo stesso modo 9,278
byte quando compilato come codice C ++. In realtà non me lo aspettavo. Pensavo che cose come EH avrebbero aggiunto almeno un po 'lì (pensavo che sarebbero almeno di cento byte diversi), anche se probabilmente era in grado di capire che non era necessario aggiungere istruzioni relative a EH poiché sono solo utilizzando la libreria standard C e non genera nulla. Ho pensato qualcosaaggiungerebbe un po 'la dimensione binaria in entrambi i modi, come RTTI. Comunque, è stato un po 'bello vederlo. Ovviamente non penso che dovresti generalizzare da questo risultato, ma almeno mi ha impressionato un po '. Inoltre non ha avuto alcun impatto sui parametri di riferimento, e naturalmente così, dal momento che immagino che le dimensioni binarie risultanti identiche significassero anche istruzioni macchina risultanti identiche.
Detto questo, a chi importa delle dimensioni binarie con i problemi di sicurezza e ingegneria sopra menzionati? Quindi, ancora una volta, scegli una lingua e abbraccia la sua filosofia invece di provare a bastardarla; questo è quello che raccomando.
//
commenti. Se funziona, perché no?