Credo di aver mescolato codice C e C ++ quando non avrei dovuto; È un problema e come correggerlo?

10

Sfondo / Scenario

Ho iniziato a scrivere un'applicazione CLI esclusivamente in C (il mio primo vero programma C o C ++ che non era "Hello World" o una sua variante). Intorno a metà stavo lavorando con "stringhe" di input dell'utente (array di caratteri) e ho scoperto l'oggetto streamer di stringa C ++. Ho visto che potevo salvare il codice usando questi, quindi li ho usati attraverso l'applicazione. Ciò significa che ho modificato l'estensione del file in .cpp e ora compilo l'app con g++ anziché gcc . Quindi, sulla base di ciò, direi che l'applicazione è ora tecnicamente un'applicazione C ++ (sebbene il 90% + del codice sia scritto in quello che chiamerei C, poiché vi è un sacco di crossover tra le due lingue data la mia limitata esperienza di il due). È un singolo file .cpp lungo circa 900 righe.

Fattori importanti

Desidero che il programma sia gratuito (come in denaro) e liberamente distribuibile e utilizzabile per tutti. La mia preoccupazione è che qualcuno guardi il codice e pensi qualcosa all'effetto di:

Oh look at the coding, it's awful, this program can't help me

Quando potenzialmente potrebbe! Un'altra questione è che il codice è efficiente (è un programma per testare la connettività Ethernet). Non ci dovrebbero essere parti del codice così inefficienti da ostacolare seriamente le prestazioni dell'applicazione o il suo output. Tuttavia, penso che sia una domanda per Stack Overflow quando chiedi aiuto con funzioni, metodi, chiamate oggetto specifiche, ecc.

La mia domanda

Avere (a mio avviso) misto C e C ++ dove forse non dovrei. Dovrei cercare di riscriverlo tutto in C ++ (con questo, voglio dire implementare più oggetti e metodi C ++ dove forse ho codificato qualcosa in uno stile C che può essere condensato usando le nuove tecniche C ++), o rimuovere l'uso di oggetti stringa streamer e portare tutto "indietro" al codice C? C'è un approccio corretto qui? Mi sono perso e ho bisogno di una guida su come mantenere questa applicazione "buona" agli occhi delle masse, quindi la useranno e ne beneficeranno.

Codice - Aggiornamento

Qui è un link al codice. Sono circa il 40% di commenti, commento quasi ogni riga finché non mi sento più fluente. Nella copia a cui mi sono collegato, ho rimosso praticamente tutti i commenti. Spero che questo non lo renda troppo difficile da leggere. Spero comunque che nessuno debba capirlo appieno. Se però ho creato fatali difetti di progettazione, spero che dovrebbero essere facilmente identificabili. Dovrei anche menzionare, sto scrivendo un paio di desktop e laptop Ubuntu. Non intendo portare il codice su altri sistemi operativi.

    
posta jwbensley 10.06.2013 - 21:23
fonte

7 risposte

13

Iniziamo dall'inizio: il codice misto C e C ++ è abbastanza comune. Quindi sei in un grande club per cominciare. Abbiamo enormi basi in codice C in natura. Ma per ovvi motivi molti programmatori si rifiutano di scrivere almeno cose nuove in C, avendo accesso a C ++ nello stesso compilatore, i nuovi moduli iniziano a essere scritti in quel modo - inizialmente lasciando solo le parti esistenti.

Quindi alla fine alcuni file esistenti vengono ricompilati come C ++ e alcuni bridge possono essere cancellati ... Ma potrebbe richiedere davvero molto tempo.

Sei un po 'avanti, il tuo sistema completo è ora C ++, solo la maggior parte è scritta "C-style". E vedi un mix di stili un problema, cosa non dovresti: C ++ è un linguaggio multi-paradigma che supporta molti stili e consente loro di coesistere per sempre. In realtà questa è la forza principale, che non sei costretto a un solo stile. Uno che sarebbe subottimale qua e là, con un po 'di fortuna non ovunque.

Rielaborare il codice base è una buona idea, SE è rotto. O se è in via di sviluppo. Ma se funziona (nel vero senso della parola), si prega di seguire il principio di ingegneria più elementare: se non è rotto, non aggiustarlo. Lascia le parti fredde da solo, metti il tuo sforzo dove conta. Sulle parti che sono cattive, pericolose o con nuove funzioni e solo parti di refactoring per trasformarle in un letto.

Se cerchi argomenti generali da affrontare, ecco cosa vale la pena sfrattare da un codice C:

  • tutte le funzioni str * e char [] - sostituiscile con una classe stringa
  • se usi sprintf, crea una versione che restituisca una stringa con il risultato, o la inserisca nella stringa e sostituisca l'uso. (Se non ti infastidisci mai con i flussi fai un favore a te stesso e salta semplicemente, a meno che non ti piacciano; gcc fornisce una sicurezza di tipo perfetta pronta per il controllo dei formati, basta aggiungere l'attributo corretto.
  • più malloc e gratuiti - NON con nuovi e cancella, ma vettoriali, elenchi, mappe e altri raccoglitori.
  • il resto della gestione della memoria (dopo i due punti precedenti deve essere piuttosto raro, coprire con puntatori intelligenti o implementare le tue raccolte speciali
  • sostituisci tutti gli altri usi delle risorse (FILE *, mutex, lock, ecc.) per usare wrapper o classi RAII

Quando hai finito ti avvicini al punto in cui il codebase può essere ragionevolmente sicuro per le eccezioni, quindi puoi abbandonare il football del codice di ritorno usando le eccezioni e raro prova / cattura in alto livello solo funzioni.

Oltre a ciò basta scrivere un nuovo codice in un C ++ sano, e se nascono alcune classi che sono buone sostitutive nel codice esistente, riprenderle.

Non ho menzionato le cose relative alla sintassi, ovviamente uso refs invece di puntatori in tutto il nuovo codice, ma sostituire le vecchie parti C solo per quel cambiamento non è un buon valore. I cast devi affrontare, eliminare tutto ciò che puoi e usare le varianti C ++ nelle funzioni wrapper per il resto. E molto importante, aggiungi const ovunque applicabile. Questi si alternano con i proiettili precedenti. Consolida le tue macro e sostituisci ciò che puoi fare in enum, inline function o template.

Suggerisco di leggere gli standard di codifica C ++ di Sutter / Alexandrescu se non sono stati ancora fatti e seguirli da vicino.

    
risposta data 10.06.2013 - 23:46
fonte
7

In breve: non stai andando all'inferno, non succederà nulla di brutto, ma non vincerai nemmeno gare di bellezza.

Mentre è perfettamente possibile usare C ++ come "C migliore", stai buttando via molti dei benefici di C ++, ma poiché non ti limiti a vaniglia C, non ottieni alcuno dei benefici di C ( semplicità, portabilità, trasparenza, interoperabilità). In altre parole: C ++ sacrifica alcune delle qualità di C per guadagnarne altre - ad esempio, la trasparenza di C in cui puoi sempre vedere chiaramente dove e quando accade l'allocazione della memoria è scambiata per le astrazioni più potenti di C ++.

Poiché il tuo codice sembra funzionare correttamente ora, probabilmente non è una buona idea riscriverlo solo per il gusto di farlo: mantienilo come è per ora, e cambialo in C ++ più idiomatico mentre vai, un pezzo in un tempo, ogni volta che lavori su una determinata parte. E tieni a mente le lezioni apprese per il tuo prossimo progetto, soprattutto questo: C e C ++ non sono la stessa lingua, e stai meglio fare una scelta in anticipo piuttosto che decidere di passare a C ++ a metà del tuo progetto .

    
risposta data 11.06.2013 - 00:15
fonte
4

C ++ è un linguaggio molto complesso, nel senso che ha molte caratteristiche. È anche un linguaggio che supporta più paradigmi, il che significa che ti permette di scrivere codice interamente procedurale senza utilizzare alcun oggetto.

Quindi, poiché il C ++ è così complesso, spesso si vede che le persone usano un certo sottoinsieme limitato delle sue funzionalità nel proprio codice. I principianti spesso usano solo l'I / O del flusso, gli oggetti stringa e new / delete invece di malloc / free, e magari riferimenti invece di puntatori. Man mano che apprendi le funzionalità orientate agli oggetti, puoi iniziare a scrivere in uno stile chiamato "C con classi". Alla fine, mentre impari di più su C ++, inizi a utilizzare i modelli, RAII , STL , puntatori intelligenti, ecc.

Il punto che sto cercando di fare è che imparare il C ++ sia un processo che richiede tempo. Sì, in questo momento il tuo codice probabilmente sembra scritto da un programmatore C che sta tentando di scrivere in C ++. E dal momento che stai imparando, va tutto bene. Tuttavia, mi fa rabbrividire, quando vedo questo genere di cose nel codice di produzione scritto da programmatori esperti che dovrebbero conoscere meglio.

Ricorda, un buon programmatore Fortran può scrivere un buon codice Fortran in qualsiasi lingua. :)

    
risposta data 10.06.2013 - 22:33
fonte
2

Sembra che tu stia ricapitolando esattamente il percorso che un sacco di vecchi programmatori C hanno preso quando andavano al C ++. Lo stai usando come "migliore C". Vent'anni fa questo aveva un senso, ma a questo punto ci sono molti più potenti costrutti in C ++ (come la Standard Template Library) che raramente ha senso ora. Come minimo, probabilmente dovresti fare quanto segue per evitare di dare aneurismi ai programmatori C ++ quando guardi il tuo codice:

  • Utilizza gli stream al posto della famiglia%?printf.
  • Utilizza new anziché malloc .
  • Utilizza le classi del contenitore STL per tutte le strutture di dati.
  • Utilizza std::string anziché char* ove possibile
  • Comprendi e utilizza RAII

Se il tuo programma è breve (e ~ 900 righe sembrano brevi) personalmente non penso che sia necessario o utile creare un insieme di classi valido.

    
risposta data 10.06.2013 - 22:37
fonte
2

La risposta accettata menziona solo i pro della conversione di C in C ++ idiomatico, come se il codice C ++ fosse in qualche modo migliore del codice C. Sono d'accordo con altre risposte che probabilmente non è necessario apportare modifiche drastiche al codice misto se non è rotto.

Se la combinazione di C e C ++ è sostenibile dipende da come viene eseguita la miscelazione. Si possono introdurre bug sottili, ad esempio quando vengono sollevate eccezioni in C-code (che non è eccezionalmente sicuro), causando perdite di memoria o corruzioni di dati. Un modo più sicuro e abbastanza comune è racchiudere le librerie C o le interfacce in classi o usarle in qualche altro tipo di isolamento in un progetto C ++.

Prima di decidere di riscrivere l'intero progetto in C ++ (o C) idiomatico, è necessario essere consapevoli che molte delle modifiche presentate in altre risposte possono rallentare il programma o causare altri effetti indesiderati. Ad esempio:

  • cambiare le stringhe di C assegnate allo stack a std :: le stringhe possono causare allocazioni di heap non necessarie
  • la modifica dei puntatori grezzi su alcuni tipi di puntatori condivisi (come std :: shared_ptr) causa un sovraccarico di accesso dovuto al conteggio dei riferimenti, alle funzioni dei membri virtuali interni e alla sicurezza dei thread
  • i flussi di libreria standard sono più lenti della controparte C
  • l'uso incauto delle classi RAII, come i contenitori, può causare operazioni non necessarie, specialmente quando non è possibile utilizzare la semantica di spostamento di C ++ 11
  • I
  • modelli possono causare tempi di compilazione più lunghi, errori di compilazione non chiari, problemi di code bloat e di portabilità tra i compilatori
  • scrivere codice eccezionalmente sicuro è difficile, il che rende facile l'introduzione di bug sottili durante la conversione
  • è più difficile usare il codice C ++ da una libreria condivisa rispetto al semplice C
  • C ++ non è ampiamente supportato come ad es. C89

Per concludere, la conversione da codice misto C e C ++ a linguaggio C ++ idiomatico dovrebbe essere vista come un compromesso che ha molto di più oltre al semplice tempo di implementazione e all'ampliamento degli strumenti con caratteristiche apparentemente convenienti. Pertanto è molto difficile dare una risposta al caso generale diverso da "dipende".

    
risposta data 28.06.2015 - 13:55
fonte
1

È una cattiva forma mescolare C e C ++. Sono lingue distinte e in realtà dovrebbero essere trattati come tali. Scegli quello con cui sei più a tuo agio e prova a scrivere un codice idiomatico in quella lingua.

Se C ++ ti fa risparmiare un sacco di codice, allora rimani con C ++ e riscrivi le parti C. Dubito che una differenza di prestazioni sarà persino evidente, se è quello che ti preoccupa.

    
risposta data 10.06.2013 - 21:48
fonte
1

Vado avanti e indietro tra C e C ++ tutto il tempo e sono il tipo strano che preferisce lavorare in questo modo. Preferisco C ++ per cose di livello superiore mentre uso C come strumento contundente che mi consente di acquisire dati di tipo radiografico e trattarli come bit e byte con, diciamo, memcpy che trovo utile per implementare strutture di dati di basso livello e allocatori di memoria. Se ti stai ritrovando a lavorare a livello di bit e byte grezzi, allora il sistema di tipo veramente ricco di C ++ non aiuta affatto nulla, e spesso trovo più facile scrivere tale codice in C, dove posso tranquillamente pensare che posso tratta qualsiasi tipo di dati C come solo bit e byte.

La cosa principale da tenere a mente è dove queste due lingue non si adattano bene.

1. Una funzione C non dovrebbe mai chiamare una funzione C ++ che può throw . Dato il numero di posizioni che il tuo tipo giornaliero di codice C ++ può implicitamente eseguire in un'eccezione, ciò significa in genere che le funzioni C non dovrebbero chiamare le funzioni C ++ in generale. Altrimenti il tuo codice C non sarà in grado di liberare risorse che alloca durante lo stack unwind poiché non ha un modo pratico per catturare l'eccezione C ++. Se finisci per richiedere che il tuo codice C richiami una funzione C ++ come callback, ad esempio, il lato C ++ dovrebbe garantire che ogni eccezione che incontra venga catturata prima di tornare al codice C.

2. Se scrivi codice C che tratta i tipi di dati come bit e byte grezzi, eseguendo il bulldozer sul sistema dei tipi (questo è in realtà il mio motivo principale per usare C in primo luogo), quindi non si vuole mai usare tale codice contro tipi di dati C ++ che potrebbero avere costruttori di copia e distruttori e puntatori virtuali e cose di quel genere che devono essere rispettati. Quindi generalmente dovrebbe essere il codice C che usa il codice C di questo tipo, non il codice C ++ che usa il codice C di questo tipo.

Se si desidera che il proprio codice C ++ utilizzi tale codice C, in genere si desidera utilizzarlo come dettaglio di implementazione di una classe che garantisce che i dati che sta memorizzando nella struttura dati C generica sia un tipo di dati che è banale da costruire e distruggere. Fortunatamente ci sono tratti di tipo come questo che puoi usare per verificare che in un asser statico, rendendo certo che i tipi che stai memorizzando nella struttura dati C generica hanno distruttori e costruttori banali e rimarranno in questo modo.

Should I look to rewrite it all in C++ (by this, I mean implement more C++ objects and methods where perhaps I have coded something in a C style that can be condensed using newer C++ techniques), or remove the use of string streamer objects and bring it all "back" to C code?

Secondo me, non devi preoccuparti se rispetti le regole precedenti e assicurati che il tuo codice sia ben testato rispetto ai test che hai scritto. La parte che potrebbe valere la pena di essere migliorata è il codice che hai scritto in C ++ fino ad ora, ma ciò non significa che devi eseguire il porting del codice che è strettamente basato su C in C ++.

Con il codice scritto in C ++ e compilato come codice C ++, devi stare attento. L'uso della codifica C-like in C ++ richiede problemi. Ad esempio, se si assegnano e si liberano le risorse manualmente, è probabile che il codice non sia sicuro per le eccezioni poiché il codice potrebbe riscontrare un'eccezione a partire dalla quale si esce implicitamente dalla funzione prima di poter sempre free della risorsa. In C ++, RAII e cose come i puntatori intelligenti non sono una soffice praticità come potrebbero apparire se si guardassero solo ai normali percorsi di esecuzione senza considerare percorsi eccezionali. Spesso sono una necessità fondamentale per scrivere codice corretto ed eccezionalmente sicuro in modo semplice ed efficace, che non si limiterà a fuoriuscire da tutte le parti quando si verificherà un'eccezione.

    
risposta data 17.12.2017 - 04:49
fonte

Leggi altre domande sui tag