È una cattiva pratica usare un compilatore C ++ solo per sovraccarico di funzioni?


60

Quindi sto lavorando a una progettazione software utilizzando C per un determinato processore. Il kit di strumenti include la possibilità di compilare C e C ++. Per quello che sto facendo, non esiste un'allocazione dinamica della memoria disponibile in questo ambiente e il programma è nel complesso abbastanza semplice. Per non parlare del fatto che il dispositivo non ha quasi potenza o risorse del processore. Non c'è davvero bisogno di usare qualsiasi C ++ di sorta.

Detto questo, ci sono alcuni posti in cui faccio sovraccarico (una caratteristica del C ++). Ho bisogno di inviare alcuni diversi tipi di dati e non mi va di usare la printfformattazione di stile con qualche tipo di %sargomento (o altro). Ho visto alcune persone che non avevano accesso a un compilatore C ++ che faceva la printfcosa, ma nel mio caso il supporto C ++ è disponibile.

Ora sono sicuro che potrei avere la domanda sul motivo per cui ho bisogno di sovraccaricare una funzione per cominciare. Quindi proverò a rispondere subito. Devo trasmettere diversi tipi di dati da una porta seriale, quindi ho alcuni sovraccarichi che trasmettono i seguenti tipi di dati:

unsigned char*
const char*
unsigned char
const char

Preferirei non avere un metodo che gestisca tutte queste cose. Quando chiamo la funzione voglio solo che trasmetta la porta seriale, non ho molte risorse, quindi non voglio fare quasi NIENTE ma la mia trasmissione.

Qualcun altro ha visto il mio programma e mi ha chiesto "perché stai usando i file CPP?" Quindi, questa è la mia unica ragione. È una cattiva pratica?

Aggiornare

Vorrei rispondere ad alcune domande poste:

Una risposta obiettiva al tuo dilemma dipenderà da:

  1. Se la dimensione dell'eseguibile aumenta in modo significativo se si utilizza C ++.

A partire da ora la dimensione dell'eseguibile consuma il 4,0% della memoria del programma (su 5248 byte) e l'8,3% della memoria dati (su 342 byte). Cioè, compilando per C ++ ... Non so come sarebbe C perché non ho usato il compilatore C. So che questo programma non crescerà più, quindi per quanto le risorse siano limitate direi che sto bene lì ...

  1. Se si riscontra un notevole impatto negativo sulle prestazioni se si utilizza C ++.

Beh, se c'è, non ho notato nulla ... ma di nuovo potrebbe essere il motivo per cui sto ponendo questa domanda poiché non capisco del tutto.

  1. Se il codice può essere riutilizzato su una piattaforma diversa in cui è disponibile solo un compilatore C.

So che la risposta a questa è sicuramente no . In realtà stiamo considerando di passare a un processore diverso, ma solo processori basati su ARM più potenti (tutti quelli che conosco per certo hanno catene di strumenti di compilazione C ++).


59
Sono stato conosciuto per usare C ++ per un progetto usando solo le funzionalità C solo per poter avere //commenti. Se funziona, perché no?
Jules,

76
La cattiva pratica sarebbe limitarsi a C quando si ha un buon uso delle funzionalità che non fornisce.
Jerry Coffin,

35
Quando dici "usa un compilatore C ++" intendi "usa C ++". Appena detto. Non puoi compilare C con un compilatore C ++, ma puoi passare facilmente da C a C ++, il che è quello che faresti effettivamente.
user253751

4
"Per quello che sto facendo, non c'è allocazione dinamica della memoria sul processore e il programma è nel complesso abbastanza semplice. Per non parlare del fatto che il dispositivo non ha quasi la potenza o le risorse del processore. Non c'è davvero bisogno di usare C ++." Spero che la prima di queste due frasi dovrebbe essere una ragione per non usare il C ++, perché sono piuttosto cattive se lo sono. Il C ++ può essere utilizzato perfettamente con i sistemi integrati.
Pharap,

21
@Jules Sono sicuro che lo sai e stavi ripensando un po ', ma nel caso qualcuno lo legga: i //commenti sono stati nello standard C dal C99.
Davislor,

Risposte:


77

Non arriverei al punto di chiamarla "cattiva pratica" di per sé , ma non sono nemmeno convinto che sia davvero la soluzione giusta al tuo problema. Se tutto ciò che vuoi sono quattro funzioni separate per eseguire i tuoi quattro tipi di dati, perché non fare ciò che i programmatori C hanno fatto da tempo immemorabile:

void transmit_uchar_buffer(unsigned char *buffer);
void transmit_char_buffer(char *buffer);
void transmit_uchar(unsigned char c);
void transmit_char(char c);

Questo è effettivamente ciò che il compilatore C ++ sta facendo comunque dietro le quinte e non è un grosso problema per il programmatore. Evita tutti i problemi di "perché stai scrivendo non abbastanza C con un compilatore C ++" e significa che nessun altro nel tuo progetto sarà confuso da quali bit di C ++ sono "consentiti" e quali bit non lo sono.


8
Direi perché no perché il tipo che viene trasmesso è (probabilmente) un dettaglio dell'implementazione, quindi nasconderlo e lasciare che il compilatore si occupi della selezione dell'implementazione potrebbe comportare un codice più leggibile. E se l'utilizzo di una funzionalità C ++ migliora la leggibilità, perché non farlo?
Jules,

29
Si può anche #definetrasmettere () usando C11_Generic
Deduplicator

16
@Jules Perché è quindi molto confuso su quali funzionalità C ++ possono essere utilizzate nel progetto. Una potenziale modifica contenente oggetti verrebbe rifiutata? E i modelli? Commenti in stile C ++? Certo, puoi aggirare questo con un documento di stile di codifica, ma se tutto ciò che stai facendo è un semplice caso di sovraccarico della funzione, scrivi semplicemente C invece.
Philip Kendall,

25
@Phillip "commenti in stile C ++" sono validi da oltre un decennio.
David Conrad,

12
@Jules: mentre il tipo che viene trasmesso è probabilmente un dettaglio di implementazione per il software che fa preventivi assicurativi, l'applicazione dei PO sembra essere un sistema incorporato che fa comunicazione seriale, dove il tipo e la dimensione dei dati sono di importanza significativa.
whatsisname

55

Usare solo alcune funzionalità di C ++ e, allo stesso tempo, trattarlo come C non è esattamente comune, ma non è nemmeno esattamente inaudito. In effetti, alcune persone non usano nemmeno alcuna funzionalità del C ++, tranne il controllo del tipo più rigoroso e più potente. Scrivono semplicemente C (avendo cura di scrivere solo nell'intersezione comune di C ++ e C), quindi compilano con un compilatore C ++ per il controllo del tipo e un compilatore C per la generazione del codice (o semplicemente attaccano con un compilatore C ++ fino in fondo).

Linux è un esempio di base di codice in cui le persone chiedono regolarmente che gli identificatori come classvengano rinominati klasso kclass, in modo che possano compilare Linux con un compilatore C ++. Ovviamente, data l'opinione di Linus sul C ++ , vengono sempre abbattuti :-D GCC è un esempio di una base di codice che è stata prima trasformata in "C ++ - clean C", e poi gradualmente riformulata per usare più funzionalità C ++.

Non c'è niente di sbagliato in quello che stai facendo. Se sei veramente paranoico sulla qualità della generazione del codice del tuo compilatore C ++, puoi usare un compilatore come Comeau C ++, che si compila in C come lingua di destinazione e quindi utilizzare il compilatore C. In questo modo, puoi anche effettuare ispezioni spot del codice per vedere se l'uso di C ++ inietta un codice sensibile alle prestazioni imprevisto. Questo non dovrebbe essere il caso del solo sovraccarico, che sta letteralmente generando automaticamente funzioni con nomi diversi - IOW, esattamente quello che faresti in C comunque.


15

Una risposta obiettiva al tuo dilemma dipenderà da:

  1. Se la dimensione dell'eseguibile aumenta in modo significativo se si utilizza C ++.
  2. Se si riscontra un notevole impatto negativo sulle prestazioni se si utilizza C ++.
  3. Se il codice può essere riutilizzato su una piattaforma diversa in cui è disponibile solo un compilatore C.

Se la risposta a una qualsiasi delle domande è "sì", è meglio creare funzioni con nomi diversi per diversi tipi di dati e attenersi a C.

Se la risposta a tutte le domande è "no", non vedo alcun motivo per cui non si debba usare C ++.


9
Devo dire che non ho mai incontrato un compilatore C ++ che genera un codice significativamente peggiore di un compilatore C per il codice scritto nel sottoinsieme condiviso delle due lingue. I compilatori C ++ ottengono una cattiva reputazione sia per dimensioni che per prestazioni, ma la mia esperienza è sempre l'uso inappropriato delle funzionalità C ++ che ha causato il problema ... In particolare, se sei preoccupato per le dimensioni, non usare iostreams e non usare modelli, ma per il resto dovresti andare bene.
Jules,

@Jules: Solo per quello che vale (non molto, IMO) ho visto ciò che è stato venduto come un singolo compilatore per C e C ++ (Turbo C ++ 1.0, se la memoria serve) produce risultati significativamente diversi per input identico. A quanto ho capito, tuttavia, questo era prima che avessero finito il loro compilatore C ++, quindi anche se sembrava un compilatore all'esterno, aveva davvero due compilatori completamente separati - uno per C, l'altro per C ++.
Jerry Coffin,

1
@JerryCoffin Se la memoria serve, i prodotti Turbo non hanno mai avuto una grande reputazione. E se fosse una versione 1.0, potresti essere scusato per non essere molto raffinato. Quindi probabilmente non è molto rappresentativo.
Barmar,

10
@Barmar: In realtà, hanno avuto una reputazione abbastanza decente per un bel po '. La loro scarsa reputazione ora è dovuta principalmente alla loro pura età. Sono competitivi con altri compilatori della stessa annata, ma nessuno pubblica domande su come fare le cose con gcc 1.4 (o qualsiasi altra cosa). Ma hai ragione, non è molto rappresentativo.
Jerry Coffin,

1
@Jules Direi che anche i template vanno bene. Nonostante la teoria popolare, l'istanza di un modello non aumenta implicitamente la dimensione del codice, la dimensione del codice generalmente non aumenta fino a quando non viene utilizzata una funzione da un modello (nel qual caso l'aumento della dimensione sarà proporzionale alla dimensione della funzione) o a meno che il modello non sia dichiarando variabili statiche. Le regole di inline continuano ad essere applicate, quindi è possibile scrivere modelli in cui tutte le funzioni sono delineate dal compilatore.
Pharap,

13

Poni la domanda come se la compilazione con C ++ ti consentisse semplicemente di accedere a più funzionalità. Questo non è il caso. Compilare con un compilatore C ++ significa che il codice sorgente viene interpretato come sorgente C ++ e C ++ è una lingua diversa da C. I due hanno un sottoinsieme comune abbastanza grande da poter essere programmato utilmente, ma ognuno ha caratteristiche che mancano agli altri, ed è possibile scrivere codice accettabile in entrambe le lingue ma interpretato diversamente.

Se davvero tutto ciò che vuoi è sovraccarico di funzioni, non vedo davvero il punto di confondere il problema inserendo C ++ in esso. Invece di funzioni diverse con lo stesso nome, distinto le loro liste di parametri, basta scrivere funzioni con nomi diversi.

Per quanto riguarda i tuoi criteri oggettivi,

  1. Se la dimensione dell'eseguibile aumenta in modo significativo se si utilizza C ++.

L'eseguibile può essere leggermente più grande quando compilato come C ++, rispetto a un codice C simile creato come tale. Come minimo, l'eseguibile C ++ avrà il dubbio vantaggio della modifica del nome C ++ per tutti i nomi di funzione, nella misura in cui qualsiasi simbolo venga mantenuto nell'eseguibile. (E questo è in realtà ciò che prevede i tuoi sovraccarichi in primo luogo.) Se la differenza sia abbastanza grande da essere importante per te è qualcosa che dovresti determinare con l'esperimento.

  1. Se si riscontra un notevole impatto negativo sulle prestazioni se si utilizza C ++.

Dubito che vedresti una notevole differenza di prestazioni per il codice che descrivi rispetto a un ipotetico analogo in puro C.

  1. Se il codice può essere riutilizzato su una piattaforma diversa in cui è disponibile solo un compilatore C.

Darei una luce leggermente diversa su questo: se vuoi collegare il codice che stai costruendo con C ++ ad altro codice, allora quell'altro codice dovrà anche essere scritto in C ++ e creato con C ++, o avrai bisogno fare una disposizione speciale nel tuo codice (dichiarando il collegamento "C"), che, inoltre, non puoi fare affatto per le tue funzioni sovraccaricate. D'altra parte, se il tuo codice è scritto in C e compilato come C, gli altri possono collegarlo con entrambi i moduli C e C ++. In genere questo tipo di problema può essere risolto girando le tabelle, ma dal momento che in realtà non sembra che abbiate bisogno del C ++, allora perché accettare un simile problema in primo luogo?


4
"L'eseguibile può essere leggermente più grande quando compilato come C ++ ... nella misura in cui eventuali simboli vengono conservati nell'eseguibile." Ad essere onesti, tuttavia, qualsiasi toolchain di ottimizzazione decente dovrebbe avere un'opzione linker per rimuovere questi simboli nelle build "release", il che significa che gonferebbero solo le build di debug. Non penso che sia una grave perdita. In realtà, è più spesso un vantaggio.
Cody Grey,

5

In passato, l'utilizzo di un compilatore C ++ come "una C migliore" è stato promosso come caso d'uso. In effetti, il primo C ++ era esattamente questo. Il principio di progettazione alla base era che potevi usare solo le funzionalità che volevi e non incorrere in costi delle funzionalità che non stavi utilizzando. Quindi, potresti sovraccaricare le funzioni (dichiarando l'intento tramite la overloadparola chiave!) E il resto del tuo progetto non solo si comporterebbe bene ma genererebbe codice non peggiore di quello che il compilatore C produrrebbe.

Da allora, le lingue sono leggermente divergenti.

Ogni mallocnel tuo codice C sarà un errore di mancata corrispondenza del tipo - non un problema nel tuo caso senza memoria dinamica! Allo stesso modo, tutti i tuoi voidpuntatori in C ti faranno inciampare, poiché dovrai aggiungere cast espliciti. Ma ... perché lo stai facendo in questo modo ... verrai guidato sempre più nel percorso dell'uso delle funzionalità C ++.

Quindi, potrebbe essere possibile con qualche lavoro extra. Ma servirà come gateway per l'adozione C ++ su larga scala. Tra qualche anno le persone si lamenteranno del tuo codice legacy che sembra sia stato scritto nel 1989, mentre sostituiscono le tue mallocchiamate new, strappano blocchi di codice di corpi di loop per chiamare invece un algoritmo e rimproverare il pericoloso polimorfismo falso che sarebbe sono stati banali se al compilatore fosse stato permesso di farlo.

D'altra parte, sai che sarebbe stato lo stesso se l'avessi scritto in C, quindi è mai sbagliato scrivere in C invece che in C ++ data la sua esistenza? Se la risposta è "no", anche l' utilizzo delle funzionalità selezionate dal C ++ non può essere sbagliato .


2

Molti linguaggi di programmazione hanno una o più culture che si sviluppano attorno a loro e hanno idee particolari su ciò che un linguaggio dovrebbe "essere". Mentre dovrebbe essere possibile, pratico e utile avere un linguaggio di basso livello adatto alla programmazione di sistemi che aumenti un dialetto di C adatto a tale scopo con alcune caratteristiche del C ++ che sarebbero ugualmente adatte, le culture che circondano le due lingue non ' favorire in particolare tale fusione.

Ho letto di un compilatore C incorporato che incorporava alcune funzionalità del C ++, inclusa la possibilità di averlo

foo.someFunction(a,b,c);

interpretato come essenzialmente

typeOfFoo_someFunction(foo, a, b, c);

così come la possibilità di sovraccaricare le funzioni statiche (i problemi di manomissione del nome sorgono solo quando vengono esportatile funzioni sono sovraccaricate). Non c'è motivo per cui i compilatori C non dovrebbero essere in grado di supportare tale funzionalità senza imporre requisiti aggiuntivi all'ambiente di collegamento e di runtime, ma il rifiuto riflessivo di molti compilatori C di quasi tutto dal C ++ nell'interesse di evitare oneri ambientali li induce a respingere anche le funzionalità che non importerebbero tali costi. Nel frattempo, la cultura del C ++ ha scarso interesse per qualsiasi ambiente che non può supportare tutte le funzionalità di quel linguaggio. A meno che o fino a quando la cultura di C non cambi per accettare tali caratteristiche, o la cultura di C ++ cambi per incoraggiare implementazioni di sottoinsiemi leggeri, il codice "ibrido" è suscettibile di soffrire di molti dei limiti di entrambe le lingue pur essendo limitato nella sua capacità di raccogliere i vantaggi di operare in un superset combinato.

Se una piattaforma supporta sia C che C ++, il codice libreria aggiuntivo richiesto per supportare C ++ probabilmente renderà l'eseguibile leggermente più grande, sebbene l'effetto non sia particolarmente grande. L'esecuzione di "caratteristiche C" all'interno di C ++ probabilmente non sarà particolarmente influenzata. Un problema più grande potrebbe essere il modo in cui gli ottimizzatori C e C ++ gestiscono vari casi angolari. Il comportamento dei tipi di dati in una C è per lo più definito dal contenuto dei bit memorizzati in essa, e C ++ ha una categoria riconosciuta di strutture (PODS - Plain Old Data Structures) le cui semantiche sono ugualmente definite per lo più dai bit memorizzati. Entrambi gli standard C e C ++, tuttavia, a volte consentono comportamenti contrari a quelli impliciti dai bit memorizzati e le eccezioni al comportamento dei "bit memorizzati" differiscono tra le due lingue.


1
Opinione interessante ma non affronta la domanda del PO.
R Sahu,

@RSahu: il codice che si adatta alla "cultura" che circonda una lingua può essere meglio supportato e più facilmente adattabile a una varietà di implementazioni rispetto al codice che non lo fa. Le culture di C e C ++ sono entrambe contrarie al tipo di utilizzo suggerito dall'OP, aggiungerò alcuni punti più specifici riguardo alle domande specifiche.
supercat

1
Si noti che esiste un gruppo incorporato / finanziario / di gioco nel comitato standard C ++ che ha lo scopo di risolvere esattamente il tipo di situazioni di "scarso interesse" che si ritiene vengano scartate.
Yakk,

@Yakk: confesserò di non aver seguito il mondo C ++ terribilmente da vicino, poiché i miei interessi sono principalmente a C; So che ci sono stati sforzi verso dialetti più integrati, ma non avevo pensato che fossero davvero arrivati ​​da nessuna parte.
supercat

2

Un altro fattore importante da considerare: chiunque erediterà il tuo codice.

Quella persona sarà sempre un programmatore C con un compilatore C? Suppongo di sì.

Quella persona sarà anche un programmatore C ++ con un compilatore C ++? Vorrei esserne ragionevolmente sicuro prima di fare dipendere il mio codice da cose specifiche di C ++.


2

Il polimorfismo è davvero una buona funzionalità che C ++ fornisce gratuitamente. Tuttavia, ci sono altri aspetti che potresti dover considerare quando scegli un compilatore. Esiste un'alternativa al polimorfismo in C ma il suo uso può causare alcune sopracciglia in rilievo. È la funzione variadica, fare riferimento al tutorial sulla funzione Variadic .

Nel tuo caso sarebbe qualcosa di simile

enum serialDataTypes
{
 INT =0,
 INT_P =1,
 ....
 }Arg_type_t;
....
....
void transmit(Arg_type_t serial_data_type, ...)
{
  va_list args;
  va_start(args, serial_data_type);

  switch(serial_data_type)
  {
    case INT: 
    //Send integer
    break;

    case INT_P:
    //Send data at integer pointer
    break;
    ...
   }
 va_end(args);
}

Mi piace l'approccio di Phillips ma sporca la tua biblioteca con molte chiamate. Con quanto sopra l'interfaccia è pulita. Ha i suoi svantaggi e alla fine è una questione di scelta.


Le funzioni Variadic servono principalmente il caso d'uso in cui sia il numero di argomenti che i loro tipi sono variabili. Altrimenti, non ne hai bisogno e, in particolare, è eccessivo per il tuo esempio particolare. Sarebbe più semplice utilizzare una normale funzione che accetta Arg_type_te void *indica i dati. Detto questo, sì, dare alla funzione (singola) un argomento che indica che il tipo di dati è effettivamente un'alternativa praticabile.
John Bollinger,

1

Per il tuo caso particolare di sovraccarico statico di una "funzione" per alcuni tipi diversi, potresti prendere in considerazione invece di utilizzare C11 con i suoi macchinari _Genericmacro . Sento che potrebbe essere sufficiente per le tue esigenze limitate.

Usando la risposta di Philip Kendall potresti definire:

#define transmit(X) _Generic((X),      \
   unsigned char*: transmit_uchar_buffer, \
   char*: transmit_char_buffer,           \
   unsigned char: transmit_uchar,         \
   char: transmit_char) ((X))

e codificare transmit(foo) qualunque sia il tipo di foo(tra i quattro tipi sopra elencati).

Se ti interessa solo i compilatori GCC (e compatibili, ad esempio Clang ), potresti considerarne __builtin_types_compatible_pl' typeofestensione.


1

È 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 memsete memcpynon 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 memsetsu 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_ptrmembro) 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:

memcpye memcmpviolare il sistema dei tipi. Usare memcpyper copiare oggetti è come fare soldi usando una fotocopiatrice. Usare memcmpper 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 memcpyuna 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 memcpytutti 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, mallocin 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 freetale memoria. Quindi, ogni volta che si dispone di un file che si basa, come C ++ con .cppun'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,728byte quando era costruito in C, e allo stesso modo 9,278byte 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.

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.