Ich glaube, ich habe C- und C ++ - Code gemischt, wenn ich es nicht hätte tun sollen. Ist das ein Problem und wie kann man es beheben?

10

Hintergrund / Szenario

Ich habe angefangen, eine CLI-Anwendung nur in C zu schreiben (mein erstes richtiges C- oder C ++ - Programm, das nicht "Hello World" oder eine Variation davon war). Ungefähr in der Mitte arbeitete ich mit "Strings" von Benutzereingaben (char-Arrays) und entdeckte das C ++ - String-Streamer-Objekt. Ich habe gesehen, dass ich mit diesen Code speichern kann, also habe ich sie über die Anwendung verwendet. Dies bedeutet, dass ich die Dateierweiterung in .cpp geändert habe und jetzt die App mit g++anstelle von kompiliere gcc. Auf dieser Grundlage würde ich sagen, dass die Anwendung jetzt technisch gesehen eine C ++ - Anwendung ist (obwohl 90% + des Codes in dem geschrieben sind, was ich C nennen würde, da es aufgrund meiner begrenzten Erfahrung mit beiden Sprachen viele Überschneidungen gibt die Zwei). Es ist eine einzelne CPP-Datei mit einer Länge von etwa 900 Zeilen.

Wichtige Faktoren

Ich möchte, dass das Programm kostenlos (wie in Geld) und frei verteilbar und für alle nutzbar ist. Ich mache mir Sorgen, dass sich jemand den Code ansieht und etwas überlegt, um Folgendes zu bewirken:

Oh, sieh dir die Codierung an, es ist schrecklich, dieses Programm kann mir nicht helfen

Wenn es möglich sein könnte! Eine andere Sache ist, dass der Code effizient ist (es ist ein Programm zum Testen der Ethernet-Konnektivität). Es sollten keine Teile des Codes vorhanden sein, die so ineffizient sind, dass sie die Leistung der Anwendung oder ihre Ausgabe erheblich beeinträchtigen können. Ich denke jedoch, dass dies eine Frage für den Stapelüberlauf ist, wenn Sie um Hilfe bei bestimmten Funktionen, Methoden, Objektaufrufen usw. bitten.

Meine Frage

Ich habe (meiner Meinung nach) C und C ++ gemischt, wo ich es vielleicht nicht sollte. Sollte ich versuchen, alles in C ++ neu zu schreiben (damit meine ich, mehr C ++ - Objekte und -Methoden zu implementieren, bei denen ich möglicherweise etwas in einem C-Stil codiert habe, das mit neueren C ++ - Techniken komprimiert werden kann), oder die Verwendung von String-Streamer-Objekten und entfernen alles "zurück" zum C-Code bringen? Gibt es hier einen richtigen Ansatz? Ich bin verloren und brauche eine Anleitung, wie ich diese Anwendung in den Augen der Massen "gut" halten kann, damit sie sie nutzen und davon profitieren können.

Der Code - Update

Hier ist ein Link zum Code. Es sind ca. 40% Kommentare, ich kommentiere fast jede Zeile, bis ich mich fließender fühle. In der Kopie, auf die ich verlinkt habe, habe ich so ziemlich alle Kommentare entfernt. Ich hoffe, das macht es nicht zu schwer zu lesen. Ich hoffe jedoch, dass niemand es vollständig verstehen muss. Wenn ich jedoch schwerwiegende Designfehler gemacht habe, hoffe ich, dass sie leicht zu identifizieren sind. Ich sollte auch erwähnen, dass ich ein paar Ubuntu-Desktops und -Laptops schreibe. Ich beabsichtige nicht, den Code auf andere Betriebssysteme zu portieren.

jwbensley
quelle
3
Ich habe CLI für Sie eindeutig definiert. CLI kann auch auf Common Language Infrastructure verweisen, was aus C-Sicht wenig sinnvoll ist.
Robert Harvey
Sie könnten es in FORTRAN umschreiben. Ich habe noch nie von OOF gehört.
ott--
2
Ich würde mir keine Sorgen machen, dass Leute Ihre Software nicht verwenden, wenn sie nicht "hübsch" ist. 99% Ihrer Benutzer sehen sich den Code nicht einmal an und kümmern sich nicht darum, wie er geschrieben ist, solange er das erreicht, wofür sie ihn benötigen. Es ist jedoch wichtig, dass der Code konsistent ist usw., damit Sie ihn langfristig pflegen können.
Evicatos
1
Warum gehst du nicht dein Ding macht freie Software (zB unter GPLv3 Lizenz), mit dem Code auf zB GitHub . Ihrem Code fehlt eine LICENSEDatei. Möglicherweise erhalten Sie interessantes Feedback.
Basile Starynkevitch

Antworten:

13

Beginnen wir am Anfang: Gemischter C- und C ++ - Code ist ziemlich häufig. Sie sind also zunächst in einem großen Club. Wir haben riesige C-Codebasen in freier Wildbahn. Aber aus offensichtlichen Gründen weigern sich viele Programmierer, zumindest neue Inhalte in C zu schreiben, da sie Zugriff auf C ++ im selben Compiler haben. Neue Module werden auf diese Weise geschrieben - zunächst werden nur die vorhandenen Teile in Ruhe gelassen.

Dann werden schließlich einige vorhandene Dateien als C ++ neu kompiliert und einige Bridges können gelöscht werden ... Aber es kann sehr lange dauern.

Sie sind etwas voraus, Ihr vollständiges System ist jetzt C ++, nur das meiste davon ist im "C-Stil" geschrieben. Und Sie sehen, dass Stilmix ein Problem ist, was Sie nicht sollten: C ++ ist eine Multi-Paradigmen-Sprache, die viele Stile unterstützt und es ihnen ermöglicht, für immer nebeneinander zu existieren. Eigentlich ist das die Hauptstärke, dass man nicht zu einem einzigen Stil gezwungen ist. Eine, die hier und da suboptimal wäre, mit etwas Glück nicht überall.

Die Codebasis zu überarbeiten ist eine gute Idee, wenn sie kaputt ist. Oder wenn es der Entwicklung im Wege steht. Aber wenn es funktioniert (im ursprünglichen Sinne des Wortes), befolgen Sie bitte das grundlegendste technische Prinzip: Wenn es nicht kaputt ist, beheben Sie es nicht. Lassen Sie die kalten Teile in Ruhe und geben Sie sich Mühe, wo es darauf ankommt. Auf den Teilen, die schlecht, gefährlich sind - oder in neuen Funktionen, und nur Teile umgestalten, um sie zu einem Bett zu machen.

Wenn Sie nach allgemeinen Fragen suchen, sollten Sie Folgendes aus einer C-Codebasis entfernen:

  • Alle str * -Funktionen und char [] - ersetzen Sie sie durch eine String-Klasse
  • Wenn Sie sprintf verwenden, erstellen Sie eine Version, die eine Zeichenfolge mit dem Ergebnis zurückgibt, oder fügen Sie sie in die Zeichenfolge ein, und ersetzen Sie die Verwendung. (Wenn Sie sich nie mit Streams beschäftigt haben, tun Sie sich selbst einen Gefallen und überspringen Sie sie einfach, es sei denn, Sie mögen sie. Gcc bietet sofort perfekte Typensicherheit für die Überprüfung von Formaten. Fügen Sie einfach das richtige Attribut hinzu.
  • die meisten malloc und kostenlos - NICHT mit neu und löschen, sondern Vektor, Liste, Karte und andere Sammlungen.
  • der Rest der Speicherverwaltung (nach den beiden vorherigen Punkten muss es ziemlich selten sein, mit intelligenten Zeigern abdecken oder Ihre speziellen Sammlungen implementieren
  • Ersetzen Sie alle anderen Ressourcennutzungen (DATEI *, Mutex, Sperre usw.), um RAII-Wrapper oder -Klassen zu verwenden

Wenn Sie damit fertig sind, nähern Sie sich dem Punkt, an dem die Codebasis einigermaßen ausnahmesicher sein kann, sodass Sie den Rückkehrcode-Fußball nur mit Ausnahmen und seltenen Versuchen / Fangen in Funktionen auf hoher Ebene löschen können.

Darüber hinaus schreiben Sie einfach neuen Code in einwandfreiem C ++, und wenn einige Klassen geboren werden, die einen guten Ersatz für vorhandenen Code darstellen, holen Sie sie ab.

Ich habe keine syntaxbezogenen Dinge erwähnt, offensichtlich Refs anstelle von Zeigern in allen neuen Codes verwendet, aber das Ersetzen alter C-Teile nur für diese Änderung ist kein guter Wert. Casts, die Sie adressieren, eliminieren und für den Rest C ++ - Varianten in Wrapper-Funktionen verwenden müssen. Und was sehr wichtig ist, fügen Sie gegebenenfalls const hinzu. Diese verschachteln mit den früheren Kugeln. Konsolidieren Sie Ihre Makros und ersetzen Sie das, was Sie in Aufzählung, Inline-Funktion oder Vorlage umwandeln können.

Ich empfehle, die C ++ - Codierungsstandards von Sutter / Alexandrescu zu lesen, falls dies noch nicht geschehen ist, und sie genau zu befolgen.

Balog Pal
quelle
Vielen Dank für die Eingabe Balog, alle guten Ratschläge in meinen Augen und ich stimme dem zu. Ich werde versuchen, den Code langsam Abschnitt für Abschnitt zu ändern, wobei ich den Arbeitscode priorisiere.
Jwbensley
7

Kurz gesagt: Du wirst nicht zur Hölle fahren, nichts Schlimmes wird passieren, aber du wirst auch keine Schönheitswettbewerbe gewinnen.

Es ist zwar durchaus möglich, C ++ als "besseres C" zu verwenden, aber Sie werfen viele der Vorteile von C ++ weg, aber weil Sie sich nicht auf Vanille C beschränken, erhalten Sie keine der Vorteile von C (Einfachheit, Portabilität) , Transparenz, Interoperabilität). Mit anderen Worten: C ++ opfert einige der Eigenschaften von C, um andere zu gewinnen. Beispielsweise wird die Transparenz von C, bei der Sie immer klar sehen können, wo und wann Speicherzuweisungen stattfinden, gegen die leistungsstärkeren Abstraktionen von C ++ eingetauscht.

Da Ihr Code jetzt in Ordnung zu sein scheint, ist es wahrscheinlich keine gute Idee, ihn nur deswegen neu zu schreiben: Behalten Sie ihn vorerst bei und ändern Sie ihn Stück für Stück in idiomatischeres C ++. wann immer Sie an einem bestimmten Teil arbeiten. Und denken Sie an die so gewonnenen Erkenntnisse für Ihr nächstes Projekt, vor allem an diese: C und C ++ sind nicht dieselbe Sprache, und Sie sollten eine Entscheidung im Voraus treffen, als sich nach der Hälfte Ihres Projekts für einen Wechsel zu C ++ zu entscheiden .

tdammers
quelle
Danke tdammers für den Rat. Ich stimme dem zu, was Sie sagen, und nehme es an Bord, danke!
Jwbensley
4

C ++ ist eine sehr komplexe Sprache in dem Sinne, dass sie viele Funktionen hat. Es ist auch eine Sprache, die mehrere Paradigmen unterstützt, was bedeutet, dass Sie vollständig prozeduralen Code schreiben können, ohne Objekte zu verwenden.

Da C ++ so komplex ist, sehen Sie häufig, dass Benutzer eine begrenzte Teilmenge seiner Funktionen in ihrem Code verwenden. Anfänger verwenden häufig nur die Stream-E / A, die Zeichenfolgenobjekte und new / delete anstelle von malloc / free und möglicherweise Verweise anstelle von Zeigern. Wenn Sie sich mit den objektorientierten Funktionen vertraut machen, können Sie mit dem Schreiben in einem Stil namens "C mit Klassen" beginnen. Wenn Sie mehr über C ++ erfahren, verwenden Sie schließlich Vorlagen, RAII , STL , intelligente Zeiger usw.

Der Punkt, den ich versuche, ist, dass das Erlernen von C ++ ein Prozess ist, der Zeit braucht. Ja, im Moment sieht Ihr Code wahrscheinlich so aus, als ob er von einem C-Programmierer geschrieben wurde, der versucht, C ++ zu schreiben. Und da Sie gerade lernen, ist das vollkommen in Ordnung. Es erschreckt mich jedoch, wenn ich so etwas im Produktionscode sehe, der von erfahrenen Programmierern geschrieben wurde, die es besser wissen sollten.

Denken Sie daran, ein guter Fortran-Programmierer kann guten Fortran-Code in jeder Sprache schreiben. :) :)

Dima
quelle
2

Es hört sich so an, als würden Sie genau den Weg rekapitulieren, den viele alte C-Programmierer eingeschlagen haben, als sie zu C ++ gewechselt sind. Sie verwenden es als "besseres C". Vor zwanzig Jahren machte dies Sinn, aber zu diesem Zeitpunkt gibt es in C ++ so viele leistungsfähigere Konstrukte (wie die Standardvorlagenbibliothek), dass es jetzt selten Sinn macht. Zumindest sollten Sie wahrscheinlich Folgendes tun, um zu vermeiden, dass C ++ - Programmierer beim Betrachten Ihres Codes Aneurysmen bekommen:

  • Verwenden Sie Streams anstelle von? printfFamilie.
  • Verwenden Sie newanstelle von malloc.
  • Verwenden Sie die STL-Containerklassen für alle Datenstrukturen.
  • Verwenden Sie std::stringstatt char*wo immer möglich
  • RAII verstehen und verwenden

Wenn Ihr Programm kurz ist (und ~ 900 Zeilen kurz erscheinen), halte ich es persönlich nicht für erforderlich oder sogar nützlich, einen robusten Satz von Klassen zu erstellen.

Gort den Roboter
quelle
Vielen Dank für den Rat, Steven. Ja, ich hatte die gleiche Vorstellung von Klassen und ich denke, ich kann den Code in eine ordentliche einzelne Datei aufräumen. Vielen Dank!
Jwbensley
2

In der akzeptierten Antwort werden nur die Vorteile der Konvertierung von C in idiomatisches C ++ erwähnt, als ob C ++ - Code in einem absoluten Sinne besser wäre als C-Code. Ich stimme anderen Antworten zu, dass es wahrscheinlich unnötig ist, drastische Änderungen am gemischten Code vorzunehmen, wenn dieser nicht kaputt ist.

Ob das Mischen von C und C ++ nachhaltig ist, hängt davon ab, wie das Mischen durchgeführt wird. Subtile Fehler können beispielsweise auftreten, wenn Ausnahmen im C-Code ausgelöst werden (was nicht ausnahmesicher ist), was zu Speicherlecks oder Datenbeschädigungen führt. Sicherer und üblicher ist es, C-Bibliotheken oder Schnittstellen in Klassen zu verpacken oder sie in einer anderen Art der Isolation in einem C ++ - Projekt zu verwenden.

Bevor Sie sich entscheiden, Ihr gesamtes Projekt in idiomatisches C ++ (oder C) umzuschreiben, sollten Sie sich darüber im Klaren sein, dass viele der in anderen Antworten enthaltenen Änderungen Ihr Programm verlangsamen oder andere unerwünschte Effekte verursachen können. Zum Beispiel:

  • Das Ändern von C-Strings mit Stapelzuweisung in std :: strings kann zu unnötigen Heap-Zuweisungen führen
  • Das Ändern von Rohzeigern auf einige gemeinsam genutzte Zeigertypen (wie std :: shared_ptr) verursacht Zugriffsaufwand aufgrund von Referenzzählung, internen Funktionen virtueller Elemente und Thread-Sicherheit
  • Standardbibliotheksströme sind langsamer als C-Gegenstücke
  • Die unachtsame Verwendung von RAII-Klassen wie Containern kann zu unnötigen Vorgängen führen, insbesondere wenn die Verschiebungssemantik von C ++ 11 nicht verwendet werden kann
  • Vorlagen können längere Kompilierungszeiten, unklare Kompilierungsfehler, Aufblähen des Codes und Portabilitätsprobleme zwischen Compilern verursachen
  • Das Schreiben von ausnahmesicherem Code ist schwierig, was das Einführen subtiler Fehler während der Konvertierung erleichtert
  • Es ist schwieriger, C ++ - Code aus einer gemeinsam genutzten Bibliothek als C zu verwenden
  • C ++ wird nicht so häufig unterstützt wie z. B. C89

Abschließend sollte die Konvertierung von gemischtem C- und C ++ - Code in idiomatisches C ++ als Kompromiss angesehen werden, der viel mehr beinhaltet als nur die Implementierungszeit und die Erweiterung Ihrer Toolbox mit scheinbar praktischen Funktionen. Daher ist es sehr schwierig, eine Antwort auf den allgemeinen Fall zu geben, außer "es kommt darauf an".

crafn
quelle
"Standard-Bibliotheksströme sind langsamer als C-Gegenstücke": Wahrscheinlich für die Internationalisierung sicherlich schwieriger als printf.
Deduplikator
1

Es ist eine schlechte Form, C und C ++ zu mischen. Sie sind verschiedene Sprachen und sollten wirklich als solche behandelt werden. Wählen Sie diejenige aus, mit der Sie am besten vertraut sind, und versuchen Sie, idiomatischen Code in dieser Sprache zu schreiben.

Wenn C ++ Ihnen viel Code spart, bleiben Sie bei C ++ und schreiben Sie die C-Teile neu. Ich bezweifle, dass ein Leistungsunterschied überhaupt spürbar sein wird, wenn Sie sich darüber Sorgen machen.

Oleksi
quelle
Ich habe also diese eine Bibliothek, die ich verwenden möchte und die in C geschrieben ist, und ich habe diese andere Bibliothek, die ich verwenden möchte, die in C ++ geschrieben ist ... Das Umschreiben von Code, der einwandfrei funktioniert, schafft nur unnötige Arbeit.
Gnasher729
1

Ich gehe die ganze Zeit zwischen C und C ++ hin und her und bin der seltsame Typ, der es bevorzugt, auf diese Weise zu arbeiten. Ich bevorzuge C ++ für übergeordnete Dinge, während ich C als stumpfes Instrument verwende, mit dem ich Datentypen röntgen und sie wie Bits und Bytes behandeln kann, mit memcpydenen ich beispielsweise Datenstrukturen und Speicherzuordnungen auf niedriger Ebene implementieren kann . Wenn Sie auf der Ebene der Rohbits und -bytes arbeiten, hilft das wirklich reichhaltige Typensystem von C ++ überhaupt nichts, und ich finde es oft einfacher, solchen Code in C zu schreiben, wo ich sicher davon ausgehen kann, dass ich es kann Behandeln Sie jeden C-Datentyp nur als Bits und Bytes.

Die Hauptsache ist, wo diese beiden Sprachen nicht gut zusammenpassen.

1. Die AC-Funktion sollte niemals eine C ++ - Funktion aufrufen, die dies kann throw. Angesichts der Anzahl der Stellen, an denen Ihre tägliche Art von C ++ - Code implizit auf eine Ausnahme stoßen kann, bedeutet dies im Allgemeinen, dass Ihre C-Funktionen im Allgemeinen keine C ++ - Funktionen aufrufen sollten. Andernfalls kann Ihr C-Code keine Ressourcen freigeben, die er beim Abwickeln des Stapels zuweist, da er die C ++ - Ausnahme nicht praktisch abfangen kann. Wenn Ihr C-Code beispielsweise eine C ++ - Funktion als Rückruf aufrufen muss, sollte die C ++ - Seite sicherstellen, dass alle aufgetretenen Ausnahmen abgefangen werden, bevor Sie zum C-Code zurückkehren.

2. Wenn Sie C-Code schreiben, der Datentypen nur als Rohbits und -bytes behandelt und über das Typsystem planiert (dies ist eigentlich mein Hauptgrund für die Verwendung von C), möchten Sie diesen Code niemals für C ++ - Daten verwenden Typen, die Kopierkonstruktoren und -destruktoren sowie virtuelle Zeiger und solche Dinge haben könnten, die respektiert werden müssen. Im Allgemeinen sollte es sich also um C-Code mit C-Code dieser Art handeln, nicht um C ++ - Code mit C-Code dieser Art.

Wenn Sie möchten, dass Ihr C ++ - Code solchen C-Code verwendet, möchten Sie ihn normalerweise als Implementierungsdetail einer Klasse verwenden, die sicherstellt, dass die Daten, die in der generischen C-Datenstruktur gespeichert werden, ein Datentyp sind, dessen Erstellung trivial ist und zerstören. Glücklicherweise gibt es Art Züge wie diese , die Sie , dass der Check - in einen statischen assert verwenden können, um sicherzustellen , dass die Typen , die Sie in die generische C - Datenstruktur zu speichern haben trivial Destruktoren und Konstrukteure und wird auch so bleiben.

Sollte ich versuchen, alles in C ++ neu zu schreiben (damit meine ich, mehr C ++ - Objekte und -Methoden zu implementieren, bei denen ich möglicherweise etwas in einem C-Stil codiert habe, das mit neueren C ++ - Techniken komprimiert werden kann), oder die Verwendung von String-Streamer-Objekten und entfernen alles "zurück" zum C-Code bringen?

Meiner Meinung nach brauchen Sie sich nicht darum zu kümmern, wenn Sie die oben genannten Regeln einhalten und sicherstellen, dass Ihr Code gegen die von Ihnen geschriebenen Tests gut getestet ist. Der Teil, der möglicherweise verbessert werden sollte, ist der Code, den Sie bisher in C ++ geschrieben haben. Dies bedeutet jedoch nicht, dass Sie den Code, der ausschließlich C-basiert ist, auf C ++ portieren müssen.

Mit dem Code, den Sie in C ++ geschrieben und als C ++ - Code kompiliert haben, müssen Sie vorsichtig sein. Die Verwendung von C-ähnlicher Codierung in C ++ ist problematisch. Wenn Sie beispielsweise Ressourcen manuell zuweisen und freigeben, besteht die Möglichkeit, dass Ihr Code nicht ausnahmesicher ist, da in Ihrem Code möglicherweise eine Ausnahme auftritt. An diesem Punkt verlassen Sie die Funktion implizit, bevor Sie freedie Ressource jemals verwenden können. In C ++ sind RAII und Dinge wie intelligente Zeiger keine einfache Annehmlichkeit, da sie möglicherweise auftreten, wenn Sie nur normale Ausführungspfade betrachten, ohne außergewöhnliche Pfade zu berücksichtigen. Sie sind oft eine grundlegende Notwendigkeit, um einfach und effektiv korrekten ausnahmesicheren Code zu schreiben, der nicht überall durchläuft, wenn eine Ausnahme auftritt.


quelle