Gibt Ihnen C # "weniger Seil zum Aufhängen" als C ++? [geschlossen]


14

Joel Spolsky charakterisierte C ++ als "genug Seil, um sich aufzuhängen" . Eigentlich fasste er "Effective C ++" von Scott Meyers zusammen:

Es ist ein Buch, das im Grunde sagt, C ++ ist genug Seil, um sich aufzuhängen, und dann ein paar zusätzliche Meilen Seil und dann ein paar Selbstmordpillen, die als M & Ms getarnt sind ...

Ich habe keine Kopie des Buches, aber es gibt Hinweise darauf, dass sich ein Großteil des Buches auf Fallstricke bei der Speicherverwaltung bezieht, die in C # offenbar in Frage gestellt werden, da die Laufzeitumgebung diese Probleme für Sie verwaltet.

Hier sind meine Fragen:

  1. Vermeidet C # Fallstricke, die in C ++ nur durch sorgfältige Programmierung vermieden werden? Wenn ja, in welchem ​​Maße und wie werden sie vermieden?
  2. Gibt es neue, unterschiedliche Fallstricke in C #, die ein neuer C # -Programmierer kennen sollte? Wenn ja, warum konnten sie durch das Design von C # nicht vermieden werden?

10
Aus der FAQ : Your questions should be reasonably scoped. If you can imagine an entire book that answers your question, you’re asking too much.. Ich glaube, dies ist eine solche Frage ...
Oded

@Oded Beziehen Sie sich auf die Überschriftenfrage mit begrenzten Zeichen? Oder meine 3+ genaueren Fragen im Text meines Posts?
Alx9r

3
Ehrlich gesagt - sowohl der Titel als auch jede der "genaueren Fragen".
Oded

3
Ich habe eine Metadiskussion zu dieser Frage gestartet .
Oded

1
In Bezug auf Ihre jetzt gelöschte dritte Frage hat mir Bill Wagners Effective C # -Serie (jetzt 3 Bücher) mehr über das Programmieren von C # beigebracht als alles andere, was ich zu diesem Thema gelesen habe. Martins Überprüfung von EC # ist richtig, da es niemals ein direkter Ersatz für Effective C ++ sein kann, aber er ist der Meinung, dass dies falsch sein sollte. Wenn Sie sich nicht mehr um die einfachen Fehler sorgen müssen, müssen Sie zu den schwierigeren Fehlern übergehen.
Mark Booth

Antworten:


33

Der grundlegende Unterschied zwischen C ++ und C # beruht auf undefiniertem Verhalten .

Es hat nichts mit der manuellen Speicherverwaltung zu tun. In beiden Fällen ist das ein gelöstes Problem.

C / C ++:

Wenn Sie in C ++ einen Fehler machen, ist das Ergebnis undefiniert.
Wenn Sie versuchen, bestimmte Annahmen über das System zu treffen (z. B. vorzeichenbehafteter Integer-Überlauf), ist Ihr Programm möglicherweise nicht definiert.

Vielleicht lesen Sie diese 3-teilige Serie über undefiniertes Verhalten.

Das macht C ++ so schnell - der Compiler muss sich keine Gedanken darüber machen, was passiert, wenn etwas schief geht, und kann so die Überprüfung auf Richtigkeit vermeiden.

C #, Java usw.

In C # wird garantiert, dass viele Fehler als Ausnahmen auftauchen, und es wird viel mehr über das zugrunde liegende System garantiert .
Dies ist eine grundlegende Barriere, um C # so schnell wie C ++ zu machen, aber es ist auch eine grundlegende Barriere, um C ++ sicherer zu machen, und es erleichtert die Arbeit mit und das Debuggen von C #.

Alles andere ist nur Soße.


Alle undefinierten Elemente werden tatsächlich von der Implementierung definiert. Wenn Sie also in Visual Studio eine Ganzzahl ohne Vorzeichen überlaufen lassen, erhalten Sie eine Ausnahme, wenn Sie die richtigen Compilerflags aktiviert haben. Jetzt weiß ich, dass es das ist, worüber Sie sprechen, aber es ist kein undefiniertes Verhalten , es ist nur so, dass die Leute normalerweise nicht danach suchen. (Dasselbe gilt für ein wirklich undefiniertes Verhalten wie operator ++. Es wird von jedem Compiler gut definiert.) Mit C # könnte man dasselbe sagen, nur gibt es nur eine Implementierung - viel undefiniertes Verhalten, wenn Sie in Mono ausgeführt werden - z. bugzilla.xamarin.com/show_bug.cgi?id=310
gbjbaanb

1
Ist es wirklich definiert oder wird es nur von der aktuellen .NET-Implementierung in der aktuellen Windows-Version definiert? Auch nicht definiertes c ++ - Verhalten ist vollständig definiert, wenn Sie es als das definieren, was g ++ tut.
Martin Beckett

6
Das Überlaufen von Ganzzahlen ohne Vorzeichen ist überhaupt nicht UB. Es ist überfüllt mit vorzeichenbehafteten ganzen Zahlen, die UB ist.
DeadMG

6
@gbjbaanb: Wie DeadMG sagte - vorzeichenbehafteter Ganzzahlüberlauf ist undefiniert. Es ist nicht implementierungsspezifisch. Diese Phrasen haben im C ++ - Standard bestimmte Bedeutungen und sind nicht dasselbe. Mach diesen Fehler nicht.
user541686

1
@CharlesSalvia: Äh, wie genau macht "C ++ es einfacher, den CPU-Cache zu nutzen" als C #? Und welche Art von Steuerelement gibt Ihnen C ++ über Speicher, den Sie in C # nicht haben können?
user541686

12

Vermeidet C # Fallstricke, die in C ++ nur durch sorgfältige Programmierung vermieden werden? Wenn ja, bis zu welchem ​​Grad und wie werden sie vermieden?

Das meiste tut es, manche tut es nicht. Und natürlich macht es einige neue.

  1. Undefiniertes Verhalten - Die größte Gefahr bei C ++ besteht darin, dass ein Großteil der Sprache undefiniert ist. Der Compiler kann das Universum buchstäblich sprengen, wenn Sie diese Dinge tun, und es wird in Ordnung sein. Dies ist natürlich ungewöhnlich, aber es ist ziemlich üblich, dass Ihr Programm auf einem Computer einwandfrei funktioniert und auf einem anderen aus keinem guten Grund. Oder schlimmer noch, auf subtile Weise anders handeln. C # weist in seiner Spezifikation einige Fälle von undefiniertem Verhalten auf, die jedoch selten sind und in Bereichen der Sprache vorkommen, die nur selten bereist werden. C ++ hat die Möglichkeit, bei jeder Aussage auf undefiniertes Verhalten zu stoßen.

  2. Speicherlecks - Dies ist für modernes C ++ weniger wichtig, aber für Anfänger und während etwa der Hälfte seiner Lebensdauer machte es C ++ sehr einfach, Speicher zu verlieren. Wirksames C ++ kam gleich um die Entwicklung von Praktiken, um dieses Problem zu beseitigen. Das heißt, C # kann immer noch Speicher verlieren. Der häufigste Fall, in den Menschen geraten, ist die Erfassung von Ereignissen. Wenn Sie über ein Objekt verfügen und eine seiner Methoden als Handler für ein Ereignis verwenden, muss der Eigentümer dieses Ereignisses über einen GC verfügen, damit das Objekt stirbt. Den meisten Anfängern ist nicht klar, dass der Event-Handler als Referenz gilt. Es gibt auch Probleme mit der Nichtverfügbarkeit von verfügbaren Ressourcen, die Speicherverluste verursachen können, aber diese sind bei weitem nicht so häufig wie Zeiger in C ++ vor Effective.

  3. Kompilierung - C ++ hat ein verzögertes Kompilierungsmodell. Dies führt zu einer Reihe von Tricks, um gut damit zu spielen und die Kompilierzeiten niedrig zu halten.

  4. Strings - Modernes C ++ verbessert dies ein wenig, ist jedoch char*für ca. 95% aller Sicherheitsverletzungen vor dem Jahr 2000 verantwortlich. Erfahrene Programmierer konzentrieren sich darauf std::string, aber es ist immer noch etwas zu vermeiden und ein Problem in älteren / schlechteren Bibliotheken . Und das betet, dass Sie keine Unicode-Unterstützung benötigen.

Und das ist wirklich die Spitze des Eisbergs. Das Hauptproblem ist, dass C ++ eine sehr schlechte Sprache für Anfänger ist. Es ist ziemlich inkonsistent und viele der alten wirklich, wirklich schlimmen Fallstricke wurden durch Ändern der Redewendungen behoben. Das Problem ist, dass Anfänger dann die Redewendungen von so etwas wie Effective C ++ lernen müssen. C # beseitigt viele dieser Probleme insgesamt und macht den Rest weniger zur Sorge, bis Sie weiter auf dem Lernweg sind.

Gibt es neue, unterschiedliche Fallstricke in C #, die ein neuer C # -Programmierer kennen sollte? Wenn ja, warum konnten sie durch das Design von C # nicht vermieden werden?

Ich erwähnte das Ereignis "Speicherverlust". Dies ist kein Sprachproblem, sondern der Programmierer erwartet etwas, was die Sprache nicht kann.

Ein weiterer Grund ist, dass der Finalizer für ein C # -Objekt technisch nicht garantiert von der Laufzeit ausgeführt werden kann. Dies spielt normalerweise keine Rolle, führt jedoch dazu, dass einige Dinge anders gestaltet werden, als Sie vielleicht erwarten.

Ein weiteres Problem, auf das Programmierer gestoßen sind, ist die Erfassungssemantik anonymer Funktionen. Wenn Sie eine Variable erfassen , erfassen Sie die Variable . Beispiel:

List<Action> actions = new List<Action>();
for(int x = 0; x < 10; ++x ){
    actions.Add(() => Console.WriteLine(x));
}

foreach(var action in actions){
    action();
}

Tut nicht was naiv gedacht wird. Dies wird 1010-mal gedruckt .

Ich bin sicher, dass es eine Reihe anderer gibt, die ich vergesse, aber das Hauptproblem ist, dass sie weniger durchdringend sind.


4
Speicherlecks gehören der Vergangenheit an char*. Ganz zu schweigen davon, dass Sie immer noch Speicher in C # verlieren können.
DeadMG

2
Vorlagen als "verherrlichtes Einfügen von Zeichenfolgen" zu bezeichnen, ist ein bisschen viel. Vorlagen sind wirklich eine der besten Funktionen von C ++.
Charles Salvia

2
@CharlesSalvia Sicher, sie sind das herausragende Merkmal von C ++. Und ja, das ist vielleicht eine Vereinfachung für die Kompilierung. Sie wirken sich jedoch überproportional auf die Kompilierungszeiten und die Ausgabegröße aus, insbesondere wenn Sie nicht vorsichtig sind.
Telastyn

2
@deadMG sicherlich, obwohl ich argumentieren würde, dass viele der in C ++ verwendeten / benötigten Template-Meta-Programmiertricks ... besser über einen anderen Mechanismus implementiert werden.
Telastyn

2
@Telastyn der ganze Sinn der type_traits ist Typinformationen zu erhalten Kompilierung , so dass Sie diese Informationen benutzen , können Dinge wie specialize Vorlagen oder Überlastung Funktionen auf bestimmte Weise zu tun mitenable_if
Charles Salvia

10

Meiner Meinung nach sind die Gefahren von C ++ etwas übertrieben.

Die wesentliche Gefahr ist folgende: Während Sie mit C # "unsichere" Zeigeroperationen mit dem unsafeSchlüsselwort ausführen können , können Sie mit C ++ (das größtenteils eine Obermenge von C darstellt) Zeiger verwenden, wann immer Sie dies wünschen. Neben den üblichen Gefahren, die mit der Verwendung von Zeigern verbunden sind (die mit C identisch sind), wie Speicherlecks, Pufferüberläufen, baumelnden Zeigern usw., bietet C ++ neue Möglichkeiten, um die Dinge ernsthaft zu vermasseln.

Dieses "zusätzliche Seil", von dem Joel Spolsky sozusagen sprach, beruht im Grunde auf einer Sache: dem Schreiben von Klassen, die intern ihr eigenes Gedächtnis verwalten, auch als " Regel von 3 " bekannt (die jetzt als Regel bezeichnet werden kann) von 4 oder Regel von 5 in C ++ 11). Das heißt, wenn Sie jemals eine Klasse schreiben möchten, die ihre eigenen Speicherzuordnungen intern verwaltet, müssen Sie wissen, was Sie tun, sonst stürzt Ihr Programm wahrscheinlich ab. Sie müssen einen Konstruktor, einen Kopierkonstruktor, einen Destruktor und einen Zuweisungsoperator sorgfältig erstellen, was überraschenderweise leicht zu Fehlern führt und zur Laufzeit oft zu bizarren Abstürzen führt.

JEDOCH in der tatsächlichen täglichen C ++ Programmierung, dann ist es sehr selten in die Tat eine Klasse zu schreiben , die einen eigenen Speicher verwaltet, so dass es irreführend zu sagen , dass C ++ Programmierer immer „vorsichtig“ sein muß , diese Fallen zu vermeiden. Normalerweise machst du einfach so etwas wie:

class Foo
{
    public:

    Foo(const std::string& s) 
        : m_first_name(s)
    { }

    private:

    std::string m_first_name;
};

Diese Klasse ähnelt in etwa Ihren Java- oder C # -Vorgängen. Sie erfordert keine explizite Speicherverwaltung (da die Bibliotheksklasse dies std::stringalles automatisch erledigt), und seit der Standardeinstellung sind überhaupt keine "3er-Regeln" erforderlich Kopierkonstruktor und Zuweisungsoperator sind in Ordnung.

Es ist nur, wenn Sie versuchen, etwas zu tun, wie:

class Foo
{
    public:

    Foo(const char* s)
    { 
        std::size_t len = std::strlen(s);
        m_name = new char[len + 1];
        std::strcpy(m_name, s);
    }

    Foo(const Foo& f); // must implement proper copy constructor

    Foo& operator = (const Foo& f); // must implement proper assignment operator

    ~Foo(); // must free resource in destructor

    private:

    char* m_name;
};

In diesem Fall kann es für Anfänger schwierig sein, die Zuweisung, den Destruktor und den Kopierkonstruktor korrekt zu machen. In den meisten Fällen gibt es jedoch keinen Grund, dies jemals zu tun. Mit C ++ können Sie die manuelle Speicherverwaltung in 99% der Fälle ganz einfach vermeiden, indem Sie Bibliotheksklassen wie std::stringund verwenden std::vector.

Ein weiteres verwandtes Problem ist die manuelle Speicherverwaltung, bei der die Möglichkeit, dass eine Ausnahme ausgelöst wird, nicht berücksichtigt wird. Mögen:

char* s = new char[100];
some_function_which_may_throw();
/* ... */
delete[] s;

Wenn some_function_which_may_throw()tatsächlich nicht eine Ausnahme auslösen, sind Sie mit einem Speicherverlust gelassen , weil der Speicher reserviert für snie zurückgewonnen werden. In der Praxis ist dies jedoch kaum noch ein Problem, da die "Regel von 3" kein wirkliches Problem mehr darstellt. Es ist sehr selten (und normalerweise unnötig), sein eigenes Gedächtnis mit rohen Zeigern zu verwalten. Um das obige Problem zu vermeiden, müssen Sie lediglich ein std::stringoder verwenden std::vector, und der Destruktor wird beim Abwickeln des Stapels nach dem Auslösen der Ausnahme automatisch aufgerufen.

Ein allgemeines Thema hier ist also, dass viele C ++ - Funktionen, die nicht von C geerbt wurden, wie automatische Initialisierung / Zerstörung, Kopierkonstruktoren und Ausnahmen, einen Programmierer dazu zwingen, bei der manuellen Speicherverwaltung in C ++ besonders vorsichtig zu sein. Dies ist jedoch wiederum nur dann ein Problem, wenn Sie zunächst die manuelle Speicherverwaltung durchführen möchten, die bei Standardcontainern und Smart Pointern kaum noch erforderlich ist.

Meiner Meinung nach ist es in C ++ zwar nicht nötig, sich daran aufzuhängen, aber in modernen C ++ sind die Fallstricke, über die Joel sprach, trivial einfach zu vermeiden.


In C ++ 03 war es die Regel von drei und in C ++ 11 ist es jetzt die Regel von vier.
DeadMG

1
Sie können es als "Regel von 5" für den Kopierkonstruktor, den Verschiebungskonstruktor, die Kopierzuweisung, die Verschiebungszuweisung und den Destruktor bezeichnen. Die Bewegungssemantik ist jedoch nicht immer nur für eine ordnungsgemäße Ressourcenverwaltung erforderlich.
Charles Salvia

Sie benötigen keine separate Zuordnung für Verschieben und Kopieren. Copy-and-Swap-Idiom kann beide Operatoren in einem erledigen.
DeadMG

2
Es beantwortet die Frage Does C# avoid pitfalls that are avoided in C++ only by careful programming?. Die Antwort ist "nicht wirklich, weil es trivial einfach ist, die Fallstricke zu vermeiden, über die Joel in modernem C ++ sprach"
Charles Salvia

1
IMO, während High-Level - Sprachen wie C # oder Java Sie mit Speicherverwaltung zur Verfügung stellen und anderen Sachen, die angeblich helfen Ihnen, ist es nicht immer so sollte tun. Am Ende kümmern Sie sich immer noch um Ihr Code-Design, damit kein Speicherverlust entsteht (was nicht genau das ist, was Sie in C ++ nennen würden). Aus meiner Erfahrung finde ich es sogar einfacher, Speicher in C ++ zu verwalten, da Sie wissen, dass Destruktoren aufgerufen werden und in den meisten Fällen die Bereinigung durchführen. Schließlich verfügt C ++ über intelligente Zeiger für Fälle, in denen das Design keine effiziente Speicherverwaltung ermöglicht. C ++ ist großartig, aber nicht für Dummies.
Pijusn

3

Ich würde nicht wirklich zustimmen. Vielleicht weniger Fallstricke als C ++ von 1985.

Vermeidet C # Fallstricke, die in C ++ nur durch sorgfältige Programmierung vermieden werden? Wenn ja, bis zu welchem ​​Grad und wie werden sie vermieden?

Nicht wirklich. Regeln wie die Rule of Three haben massive Bedeutung in C ++ 11 dank verloren unique_ptrund shared_ptrist standardisiert. Eine vage sinnvolle Verwendung der Standardklassen ist keine "sorgfältige Codierung", sondern eine "grundlegende Codierung". Außerdem ist der Anteil der C ++ - Benutzer, die immer noch dumm, nicht informiert oder beides sind, um Dinge wie die manuelle Speicherverwaltung auszuführen, viel geringer als zuvor. Die Realität ist, dass Dozenten, die solche Regeln demonstrieren möchten, Wochen damit verbringen müssen, Beispiele zu finden, bei denen sie noch Anwendung finden, da die Standardklassen praktisch jeden erdenklichen Anwendungsfall abdecken. Viele effektive C ++ - Techniken sind den gleichen Weg gegangen - den Weg des Dodos. Viele der anderen sind nicht wirklich so C ++ spezifisch. Lass mich sehen. Überspringen des ersten Elements, die nächsten zehn sind:

  1. Codieren Sie C ++ nicht so, wie es C ist. Das ist wirklich nur gesunder Menschenverstand.
  2. Schränken Sie Ihre Schnittstellen ein und verwenden Sie die Kapselung. OOP.
  3. Zwei-Phasen-Initialisierungscode-Schreiber sollten auf dem Spiel stehen. OOP.
  4. Wissen, was Wertsemantik ist. Ist das wirklich C ++ spezifisch?
  5. Schränken Sie Ihre Schnittstellen erneut ein, diesmal auf etwas andere Weise. OOP.
  6. Virtuelle Destruktoren. Ja. Dieser ist wahrscheinlich noch gültig - etwas. finalund overridehaben geholfen, dieses besondere Spiel zum Besseren zu verändern. Machen Sie Ihren Destruktor overrideund Sie garantieren einen netten Compilerfehler, wenn Sie von jemandem erben, der seinen Destruktor nicht gemacht hat virtual. Machen Sie Ihre Klasse finalund keine schlechte Peeling kann mitkommen und aus Versehen ohne einen virtuellen Destruktor erben.
  7. Schlimme Dinge passieren, wenn die Bereinigungsfunktionen fehlschlagen. Dies ist nicht wirklich spezifisch für C ++ - Sie können die gleichen Ratschläge für Java und C # sehen - und, na ja, so ziemlich jede Sprache. Bereinigungsfunktionen zu haben, die fehlschlagen können, ist schlichtweg schlecht und es gibt weder C ++ noch OOP an diesem Element.
  8. Beachten Sie, wie die Reihenfolge der Konstruktoren die virtuellen Funktionen beeinflusst. Komischerweise würde es in Java (entweder aktuell oder in der Vergangenheit) die Funktion der abgeleiteten Klasse einfach fälschlicherweise aufrufen, was sogar noch schlimmer ist als das Verhalten von C ++. Unabhängig davon ist dieses Problem nicht spezifisch für C ++.
  9. Bedienerüberladungen sollten sich wie erwartet verhalten. Nicht wirklich spezifisch. Zum Teufel, es ist kaum ein Operator, der spezifisch überlädt. Dasselbe könnte auf jede Funktion angewendet werden. Geben Sie ihm keinen Namen und lassen Sie ihn dann etwas völlig Unintuitives tun.
  10. Dies wird derzeit als schlechte Praxis angesehen. Alle stark ausnahmesicheren Zuweisungsoperatoren behandeln die Selbstzuweisung in Ordnung, und die Selbstzuweisung ist praktisch ein logischer Programmfehler, und die Überprüfung auf Selbstzuweisung ist die Leistungskosten einfach nicht wert.

Natürlich werde ich nicht jedes einzelne Effective C ++ - Element durchgehen, aber die meisten von ihnen wenden einfach grundlegende Konzepte auf C ++ an. Dieselben Ratschläge finden Sie in jeder objektorientierten überladbaren Operator-Sprache mit Werttyp. Virtuelle Destruktoren sind die einzigen, die eine C ++ - Falle darstellen und immer noch gültig sind - obwohl sie mit der finalKlasse von C ++ 11 wohl nicht so gültig sind wie sie waren. Denken Sie daran, dass Effective C ++ geschrieben wurde, als die Idee, OOP und die spezifischen Funktionen von C ++ anzuwenden, noch sehr neu war. In diesen Artikeln geht es kaum um die Fallstricke von C ++ und mehr darum, wie man mit dem Wechsel von C umgeht und wie man OOP richtig einsetzt.

Bearbeiten: Die Fallstricke von C ++ beinhalten keine Dinge wie die Fallstricke von malloc. Ich meine zum einen, dass jede einzelne Falle, die Sie in C-Code finden, die Sie auch in unsicherem C # -Code finden, nicht besonders relevant ist, und zum anderen, nur weil der Standard sie für die Interoperation definiert, bedeutet dies nicht, dass die Verwendung als C ++ gilt Code. Der Standard definiert gotoauch, aber wenn Sie einen riesigen Haufen Spaghetti-Chaos damit schreiben würden, würde ich das für Ihr Problem halten, nicht für das der Sprache. Es gibt einen großen Unterschied zwischen "sorgfältiger Codierung" und "Befolgen grundlegender Redewendungen der Sprache".

Gibt es neue, unterschiedliche Fallstricke in C #, die ein neuer C # -Programmierer kennen sollte? Wenn ja, warum konnten sie durch das Design von C # nicht vermieden werden?

usingsaugt. Macht es wirklich. Und ich habe keine Ahnung, warum etwas Besseres nicht gemacht wurde. Auch Base[] = Derived[]und so ziemlich jede Verwendung von Object, die vorhanden ist, weil die ursprünglichen Designer den massiven Erfolg der Vorlagen in C ++ nicht bemerkten, und entschieden, dass "Wir müssen einfach alles von allem erben und unsere gesamte Typensicherheit verlieren" die klügere Wahl war . Ich glaube auch, dass Sie einige böse Überraschungen in Sachen Rennbedingungen mit Delegierten und anderem solchen Spaß finden können. Dann gibt es noch andere allgemeine Dinge, wie zum Beispiel, wie schrecklich Generika im Vergleich zu Vorlagen saugen, das wirklich wirklich unnötige erzwungene Platzieren von allem in einem classund dergleichen.


5
Eine gut ausgebildete Benutzerbasis oder neue Konstrukte verringern das Seil jedoch nicht wirklich. Sie sind nur Workarounds, damit weniger Menschen aufgehängt werden. Dies ist jedoch alles ein guter Kommentar zu Effective C ++ und seinem Kontext in der Sprachentwicklung.
Telastyn

2
Nein. Es geht darum, wie viele der Elemente in Effective C ++ Konzepte sind, die gleichermaßen für jede objektorientierte Sprache mit Werttyp gelten können. Und wenn Sie die Benutzerbasis so ausbilden, dass sie tatsächlich C ++ anstelle von C codiert, wird das Seil, das Ihnen C ++ gibt, definitiv kleiner. Auch würde ich erwarten , dass neue Sprachkonstrukte sind , das Seil zu verringern. Es geht darum, wie nur, weil der C ++ Standard definiert malloc, nicht bedeutet, dass Sie es tun sollten, und nicht nur, weil Sie gotowie eine Hündin huren können, bedeutet, dass es ein Seil ist, an dem Sie sich aufhängen können.
DeadMG

2
Die Verwendung der C-Teile von C ++ unterscheidet sich nicht vom Schreiben Ihres gesamten Codes unsafein C #, was genauso schlecht ist. Wenn Sie möchten, könnte ich auch jede Falle auflisten, in der C # wie C codiert wird.
DeadMG

@DeadMG: so wirklich die Frage sollte „ein C ++ Programmierer hat genug Seil , sich zu erhängen, solange er einen C - Programmierer ist“
gbjbaanb

"Außerdem ist der Anteil der C ++ - Benutzer, die immer noch dumm, nicht informiert oder beides sind, um Dinge wie die manuelle Speicherverwaltung auszuführen, viel geringer als zuvor." Zitat benötigt.
Dan04

3

Vermeidet C # Fallstricke, die in C ++ nur durch sorgfältige Programmierung vermieden werden? Wenn ja, bis zu welchem ​​Grad und wie werden sie vermieden?

C # hat die Vorteile von:

  • Nicht abwärtskompatibel mit C, um zu vermeiden, dass eine lange Liste von "bösen" Sprachfunktionen (z. B. rohen Zeigern) vorhanden ist, die syntaktisch praktisch sind, jetzt aber als schlechter Stil gelten.
  • Referenzsemantik anstelle von Wertsemantik, wodurch mindestens 10 der effektiven C ++ - Elemente in Frage gestellt werden (was jedoch neue Fallstricke mit sich bringt ).
  • Weniger implementierungsdefiniertes Verhalten als in C ++.
    • Insbesondere in C ++ die Zeichenkodierung char, stringusw. ist die Implementierung definiert. Das Schisma zwischen der Windows-Herangehensweise an Unicode ( wchar_tfür UTF-16, charfür veraltete "Codepages") und der * nix-Herangehensweise (UTF-8) verursacht große Schwierigkeiten bei plattformübergreifendem Code. C #, OTOH, garantiert, dass a stringUTF-16 ist.

Gibt es neue, unterschiedliche Fallstricke in C #, die ein neuer C # -Programmierer kennen sollte?

Ja: IDisposable

Gibt es ein gleichwertiges Buch zu "Effective C ++" für C #?

Es gibt ein Buch namens Effective C #, das in seiner Struktur Effective C ++ ähnelt .


0

Nein, C # (und Java) sind weniger sicher als C ++

C ++ ist lokal überprüfbar . Ich kann eine einzelne Klasse in C ++ untersuchen und feststellen, dass in der Klasse weder Speicher noch andere Ressourcen verloren gehen, sofern alle Klassen, auf die verwiesen wird, korrekt sind. In Java oder C # muss jede referenzierte Klasse überprüft werden, um festzustellen, ob eine Finalisierung erforderlich ist.

C ++:

{
   some_resource r(...);  // resource initialized
   ...
}  // resource destructor called, no leaks here

C #:

{
   SomeResource r = new SomeResource(...); // resource initialized
   ...
} // did I need to finalize that?  May I should have used 'using' 
  // (or in Java, a grotesque try/finally construct)?  No way to tell
  // without checking the documentation for SomeResource

C ++:

{
    auto_ptr<SomeInterface> i = SomeFactory.create(...);
    i->f(...);
} // automatic finalization and memory release.  A new implementation of
  // SomeInterface can allocate and free resources with no impact
  // on existing code

C #:

{
   SomeInterface i = SomeFactory.create(...);
   i.f(...);
   ...
} // Sure hope someone didn't create an implementation of SomeInterface
  // that requires finalization.  In C# and Java it is necessary to decide whether
  // any implementation could require finalization when the interface is defined.
  // If the initial decision is 'no finalization', then no future implementation  
  // can acquire any resource without creating potential leaks in existing code.

3
... es ist in modernen IDEs ziemlich trivial festzustellen, ob etwas von IDisposable erbt. Das Hauptproblem ist, dass Sie wissen müssen, um zu verwenden auto_ptr(oder einige seiner Verwandten). Das ist das sprichwörtliche Seil.
Telastyn

2
@Telastyn Nein, der Punkt ist, dass Sie immer einen intelligenten Zeiger verwenden, es sei denn, Sie wissen wirklich, dass Sie keinen benötigen. In C # entspricht die using-Anweisung genau dem Seil, auf das Sie sich beziehen. (dh in C ++ müssen Sie daran denken, einen intelligenten Zeiger zu verwenden, warum ist C # dann nicht so schlecht, obwohl Sie daran denken müssen, immer eine using-Anweisung zu verwenden)
gbjbaanb

1
@gbjbaanb Weil das was? Sind höchstens 5% der C # -Klassen verfügbar? Und Sie wissen, dass Sie sie entsorgen müssen, wenn sie wegwerfbar sind. In C ++ ist jedes einzelne Objekt verfügbar. Und Sie wissen nicht , ob Ihre spezielle Instanz behandelt werden muss. Was passiert mit zurückgegebenen Zeigern, die nicht aus einer Fabrik stammen? Ist es deine Verantwortung, sie zu säubern? Es sollte nicht sein, aber manchmal ist es. Nur weil Sie immer einen intelligenten Zeiger verwenden sollten, bedeutet dies nicht, dass die Option nicht aufhört zu existieren. Dies ist insbesondere für Anfänger eine erhebliche Gefahr.
Telastyn

2
@Telastyn: Zu wissen, wie man es benutzt, auto_ptrist so einfach wie zu wissen, wie man es benutzt IEnumerableoder wie man es benutzt, oder keine Gleitkommazahlen für Währungen oder ähnliches zu benutzen . Es ist eine grundlegende Anwendung von DRY. Niemand, der die Grundlagen des Programmierens kennt, würde diesen Fehler machen. Im Gegensatz zu using. Das Problem dabei usingist, dass Sie für jede Klasse wissen müssen, ob sie verfügbar ist oder nicht (und ich hoffe, dass sich dies niemals ändert). Wenn sie nicht verfügbar ist, sperren Sie automatisch alle abgeleiteten Klassen, die möglicherweise verfügbar sein müssen.
DeadMG

2
kevin: Äh, deine Antwort macht keinen Sinn. Es ist nicht C # 's Schuld, dass Sie es falsch machen. Sie sind NICHT auf Finalizer in ordnungsgemäß geschriebenem C # -Code angewiesen . Wenn Sie ein Feld mit einer DisposeMethode haben, müssen Sie diese implementieren IDisposable(auf die richtige Art und Weise). Wenn Ihre Klasse dies tut (was der Implementierung von RAII für Ihre Klasse in C ++ entspricht) und Sie dies verwenden using(wie die intelligenten Zeiger in C ++), funktioniert alles perfekt. Der Finalizer ist hauptsächlich dazu gedacht, Unfälle zu vermeiden. Er Disposeist für die Richtigkeit verantwortlich. Wenn Sie ihn nicht verwenden, liegt das an Ihnen, nicht an C #.
user541686

0

Ja 100% ja, da ich denke, dass es unmöglich ist, Speicher freizugeben und in C # zu verwenden (vorausgesetzt, es wird verwaltet und Sie wechseln nicht in den unsicheren Modus).

Aber wenn Sie wissen, wie man in C ++ programmiert, was eine unglaubliche Anzahl von Leuten nicht tut. Du bist ziemlich in Ordnung. Wie Charles Salvia verwalten Klassen ihre Erinnerungen nicht wirklich, da alles in bereits existierenden STL-Klassen behandelt wird. Ich benutze selten Zeiger. In der Tat ging ich Projekte ohne einen einzigen Zeiger. (C ++ 11 macht dies einfacher).

if (i=0)Der Compiler beklagt sich über Tippfehler, dumme Fehler usw. (zum Beispiel, dass der Schlüssel hängen geblieben ist, als Sie schnell auf == geklickt haben). Andere Beispiele sind das Vergessen breakin switch-Anweisungen und das Nicht-Deklarieren von statischen Variablen in einer Funktion (was ich manchmal nicht mag, aber eine gute Idee imo ist).


4
Java und C # machten das =/ ==Problem noch schlimmer durch die Verwendung ==als Referenz Gleichheit und Einführung .equalsfür Wertgleichheit. Der arme Programmierer muss jetzt verfolgen, ob eine Variable 'double' oder 'Double' ist und die richtige Variante aufrufen.
Kevin Cline

@kevincline +1, aber in C # structkann man ==das unglaublich gut machen, da man die meiste Zeit nur Strings, Ints und Floats hat (dh nur Strukturelemente). In meinem eigenen Code bekomme ich dieses Problem nie, es sei denn, ich möchte Arrays vergleichen. Ich glaube nicht, dass ich jemals Listen- oder Nicht-Strukturtypen (String, Int, Float, DateTime, KeyValuePair und viele andere) vergleiche

2
Python hat es richtig gemacht, indem es ==für die Wertgleichheit und isfür die Referenzgleichheit verwendet hat.
Dan04

@ dan04 - Wie viele Arten von Gleichheit gibt es Ihrer Meinung nach in C #? Sehen Sie sich das exzellente AKKU-Blitzgespräch an: Einige Objekte sind gleichberechtigter als andere
Mark Booth
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.