Welche C ++ - Fallstricke sollte ich vermeiden? [geschlossen]

74

Ich erinnere mich, dass ich zuerst etwas über Vektoren in der STL gelernt habe und nach einiger Zeit einen Vektor von Bools für eines meiner Projekte verwenden wollte. Nachdem ich ein seltsames Verhalten gesehen und Nachforschungen angestellt hatte, stellte ich fest, dass ein Vektor von Bools nicht wirklich ein Vektor von Bools ist .

Gibt es andere häufige Fallstricke, die in C ++ vermieden werden sollten?

Craig H.
quelle
42
Ich dachte, C ++ ist die Falle, die Sie vermeiden sollten.
Fröhlich
Es ist amüsant, die Antworten vor dem Hintergrund der Berufserfahrung mit eingebetteten Systemen zu lesen. (Selbst wenn diese eingebetteten Systeme viele Prozessoren und eine Menge Speicher haben.)
Dash-Tom-Bang
Dies ist eine rhetorische Frage, die verwendet wird, um eine Diskussion zu initiieren. Sie steht unter den Kriterien für den engen Grund "keine echte Frage" und scheint besser für Ihr Blog oder eine Website geeignet zu sein, die der Diskussion gewidmet ist .
10
Äh .. alle von ihnen ..?
Bobobobo

Antworten:

76

Eine kurze Liste könnte sein:

  • Vermeiden Sie Speicherverluste, indem Sie gemeinsam genutzte Zeiger verwenden, um die Speicherzuweisung und -bereinigung zu verwalten
  • Verwenden Sie die RAII-Redewendung ( Resource Acquisition Is Initialization ), um die Ressourcenbereinigung zu verwalten - insbesondere bei Ausnahmen
  • Vermeiden Sie das Aufrufen virtueller Funktionen in Konstruktoren
  • Verwenden Sie nach Möglichkeit minimalistische Codierungstechniken, z. B. das Deklarieren von Variablen nur bei Bedarf, das Scoping von Variablen und das frühzeitige Design, sofern dies möglich ist.
  • Verstehen Sie die Ausnahmebehandlung in Ihrem Code wirklich - sowohl in Bezug auf Ausnahmen, die Sie auslösen, als auch in Bezug auf Ausnahmen, die von Klassen ausgelöst werden, die Sie möglicherweise indirekt verwenden. Dies ist besonders wichtig, wenn Vorlagen vorhanden sind.

RAII, gemeinsam genutzte Zeiger und minimalistische Codierung sind natürlich nicht spezifisch für C ++, aber sie helfen, Probleme zu vermeiden, die bei der Entwicklung in der Sprache häufig auftreten.

Einige ausgezeichnete Bücher zu diesem Thema sind:

  • Effektives C ++ - Scott Meyers
  • Effektiver C ++ - Scott Meyers
  • C ++ - Codierungsstandards - Sutter & Alexandrescu
  • C ++ FAQs - Cline

Das Lesen dieser Bücher hat mir mehr als alles andere geholfen, die Art von Fallstricken zu vermeiden, nach denen Sie fragen.

Brian Stewart
quelle
Sie haben die richtigen und besten Bücher angegeben, nach denen ich gesucht habe. :)
Namratha Patil
6
"Vermeiden Sie das Aufrufen virtueller Funktionen in Konstruktoren" <- Ich würde diese von "Vermeiden" auf "Nie" aktualisieren. +1 obwohl. (Nämlich weil es undefiniertes Verhalten ist)
Billy ONeal
Vielleicht virtuelle Destruktoren einschließen und wie man Ausnahmen richtig fängt (und neu wirft)?
Asgeir S. Nilsen
4
@ BillyONeal Ich würde es wahrscheinlich "vermeiden" lassen. In jedem Fall ist das Verhalten für virtuelle Aufrufe in Konstruktoren gut definiert. Ein solcher Aufruf ist kein undefiniertes Verhalten, es sei denn, der Aufruf erfolgt an eine reine virtuelle Funktion innerhalb eines Konstruktors einer reinen virtuellen Klasse (und analog für Destruktoren)
Johannes Schaub - litb
Ein weiteres großartiges Buch von Meyers ist natürlich Effective STL! Könnte auch hinzufügen, da es jetzt heraus ist, Effective Modern C ++
aho
52

Fallstricke in absteigender Reihenfolge ihrer Bedeutung

Zunächst sollten Sie die preisgekrönten C ++ - FAQ besuchen . Es hat viele gute Antworten auf Fallstricke. Wenn Sie weitere Fragen haben, rufen Sie ##c++auf irc.freenode.orgim IRC . Wir helfen Ihnen gerne weiter, wenn wir können. Beachten Sie, dass alle folgenden Fallstricke ursprünglich geschrieben wurden. Sie werden nicht nur aus zufälligen Quellen kopiert.


delete[]weiter new, deleteweiternew[]

Lösung : Wenn Sie die oben genannten Schritte ausführen, erhalten Sie ein undefiniertes Verhalten: Alles könnte passieren. Verstehe deinen Code und was er tut und immer delete[]was du new[]und deletewas du new, dann wird das nicht passieren.

Ausnahme :

typedef T type[N]; T * pT = new type; delete[] pT;

Sie müssen, delete[]obwohl Sie new, da Sie ein Array neu gemacht haben. Wenn Sie also mit arbeiten typedef, seien Sie besonders vorsichtig.


Aufruf einer virtuellen Funktion in einem Konstruktor oder Destruktor

Lösung : Beim Aufrufen einer virtuellen Funktion werden die überschreibenden Funktionen in den abgeleiteten Klassen nicht aufgerufen. Das Aufrufen einer rein virtuellen Funktion in einem Konstruktor oder Deskriptor ist ein undefiniertes Verhalten.


Aufruf deleteoder delete[]auf einen bereits gelöschten Zeiger

Lösung : Weisen Sie jedem gelöschten Zeiger 0 zu. Ein Aufruf deleteoder delete[]ein Nullzeiger bewirkt nichts.


Nehmen Sie die Größe eines Zeigers, wenn die Anzahl der Elemente eines 'Arrays' berechnet werden soll.

Lösung : Übergeben Sie die Anzahl der Elemente neben dem Zeiger, wenn Sie ein Array als Zeiger an eine Funktion übergeben müssen. Verwenden Sie die hier vorgeschlagene Funktion , wenn Sie die Größe eines Arrays annehmen, das eigentlich ein Array sein soll.


Verwenden eines Arrays als wäre es ein Zeiger. Somit wird T **für ein zweidimensionales Array verwendet.

Lösung : Hier erfahren Sie, warum sie unterschiedlich sind und wie Sie damit umgehen.


Schreiben in ein String-Literal: char * c = "hello"; *c = 'B';

Lösung : Ordnen Sie ein Array zu, das aus den Daten des Zeichenfolgenliteral initialisiert wurde, und schreiben Sie darauf:

char c[] = "hello"; *c = 'B';

Das Schreiben in ein String-Literal ist ein undefiniertes Verhalten. Auf jeden Fall ist die obige Konvertierung von einem String-Literal in char *veraltet. Compiler werden also wahrscheinlich warnen, wenn Sie die Warnstufe erhöhen.


Ressourcen erstellen und dann vergessen, sie freizugeben, wenn etwas wirft.

Lösung : Verwenden Sie intelligente Zeiger wie std::unique_ptroder std::shared_ptrwie in anderen Antworten angegeben.


Ändern eines Objekts zweimal wie in diesem Beispiel: i = ++i;

Lösung : Das obige sollte idem Wert von zugewiesen werden i+1. Aber was es tut, ist nicht definiert. Anstatt idas Ergebnis zu erhöhen und zuzuweisen, ändert es sich auch iauf der rechten Seite. Das Ändern eines Objekts zwischen zwei Sequenzpunkten ist ein undefiniertes Verhalten. Sequenzpunkte umfassen ||, &&, comma-operator, semicolonund entering a function(nicht erschöpfende Liste!). Ändern Sie den Code wie folgt, damit er sich korrekt verhält:i = i + 1;


Verschiedene Probleme

Vergessen, Streams zu spülen, bevor eine Blockierungsfunktion wie aufgerufen wird sleep.

Lösung : Leeren Sie den Stream, indem Sie ihn entweder std::endlanstelle \noder durch Aufrufen streamen stream.flush();.


Deklarieren einer Funktion anstelle einer Variablen.

Lösung : Das Problem tritt auf, weil der Compiler beispielsweise interpretiert

Type t(other_type(value));

als Funktion Erklärung einer Funktion tzurückkehrt Typeund einen Parameter des Typs aufweist , other_typedie aufgerufen wird value. Sie lösen es, indem Sie das erste Argument in Klammern setzen. Jetzt erhalten Sie eine Variable tvom Typ Type:

Type t((other_type(value)));

Aufruf der Funktion eines freien Objekts, das nur in der aktuellen Übersetzungseinheit ( .cppDatei) deklariert ist .

Lösung : Der Standard definiert nicht die Reihenfolge der Erstellung freier Objekte (im Namespace-Bereich), die für verschiedene Übersetzungseinheiten definiert sind. Das Aufrufen einer Elementfunktion für ein noch nicht erstelltes Objekt ist ein undefiniertes Verhalten. Sie können stattdessen die folgende Funktion in der Übersetzungseinheit des Objekts definieren und von anderen aufrufen:

House & getTheHouse() { static House h; return h; }

Dadurch wird das Objekt bei Bedarf erstellt und Sie erhalten zum Zeitpunkt des Aufrufs von Funktionen ein vollständig erstelltes Objekt.


Definieren einer Vorlage in einer .cppDatei, während sie in einer anderen .cppDatei verwendet wird.

Lösung : Fast immer erhalten Sie Fehler wie undefined reference to .... Fügen Sie alle Vorlagendefinitionen in einen Header ein, damit der Compiler, wenn er sie verwendet, bereits den erforderlichen Code erzeugen kann.


static_cast<Derived*>(base);Wenn base ein Zeiger auf eine virtuelle Basisklasse von ist Derived.

Lösung : Eine virtuelle Basisklasse ist eine Basis, die nur einmal vorkommt, auch wenn sie mehr als einmal von verschiedenen Klassen indirekt in einem Vererbungsbaum geerbt wird. Dies ist nach dem Standard nicht zulässig. Verwenden Sie dazu dynamic_cast und stellen Sie sicher, dass Ihre Basisklasse polymorph ist.


dynamic_cast<Derived*>(ptr_to_base); wenn die Base nicht polymorph ist

Lösung : Der Standard erlaubt keine Herabsetzung eines Zeigers oder einer Referenz, wenn das übergebene Objekt nicht polymorph ist. Es oder eine seiner Basisklassen muss eine virtuelle Funktion haben.


Ihre Funktion akzeptieren lassen T const **

Lösung : Sie denken vielleicht, dass dies sicherer ist als die Verwendung T **, aber tatsächlich verursacht es Kopfschmerzen für Personen, die bestehen möchten T**: Der Standard erlaubt dies nicht. Es gibt ein gutes Beispiel dafür, warum es nicht erlaubt ist:

int main() {
    char const c = ’c’;
    char* pc;
    char const** pcc = &pc; //1: not allowed
    *pcc = &c;
    *pc = ’C’; //2: modifies a const object
}

Akzeptiere T const* const*;stattdessen immer.

Ein weiterer (geschlossener) Fallstrick-Thread zu C ++, damit Leute, die nach ihnen suchen, sie finden, sind die C ++ - Fallstricke der Stapelüberlauffrage .

Johannes Schaub - litb
quelle
1
a [i] = ++ i; // Das zweimalige Lesen einer geänderten Variablen führt zu undefiniertem Verhalten ... Sie können dies auch hinzufügen, wenn Sie dies wünschen
yesraaj
2
+1, viele gute Punkte. Das über das Mischen von typedef und delete [] war für mich völlig neu! Noch ein
Eckfall,
2
"Weisen Sie jedem Zeiger, den Sie löschen, 0 zu." <- Entschuldigung, aber falsch. Die einzige Lösung besteht darin, den Fehler überhaupt nicht zu schreiben. Es ist durchaus möglich, dass jemand eine Kopie dieses Zeigers erstellt hat, die nicht davon betroffen ist, dass Sie ihn auf Null setzen.
Billy ONeal
@BillyONeal, Sie können nicht erkennen, ob Sie einen Zeiger bereits gelöscht haben, wenn Sie ihn nach dem Löschen nicht auf null setzen. Es ist nicht unbedingt ein Fehler, zweimal zu löschen, wenn Sie ihn danach einfach auf null setzen, daher meine vorgeschlagene Lösung.
Johannes Schaub - litb
@ Johannes Schaub - litb: Stimmt, aber mein Punkt ist, dass das nicht narrensicher ist. Wenn jemand eine Kopie des Zeigers hat und versucht, ihn zu löschen, haben Sie immer noch doppelte Probleme.
Billy ONeal
12

Brian hat eine großartige Liste: Ich würde hinzufügen "Markiere einzelne Argumentkonstruktoren immer explizit (außer in den seltenen Fällen, in denen du automatisches Casting willst)."

0124816
quelle
8

Nicht wirklich ein spezifischer Tipp, aber eine allgemeine Richtlinie: Überprüfen Sie Ihre Quellen. C ++ ist eine alte Sprache und hat sich im Laufe der Jahre stark verändert. Best Practices haben sich damit geändert, aber leider gibt es immer noch viele alte Informationen. Hier gab es einige sehr gute Buchempfehlungen - ich kann jedes Scott Meyers C ++ - Buch als zweites kaufen. Machen Sie sich mit Boost und den in Boost verwendeten Codierungsstilen vertraut - die an diesem Projekt beteiligten Personen sind auf dem neuesten Stand des C ++ - Designs.

Das Rad nicht neu erfinden. Machen Sie sich mit STL und Boost vertraut und nutzen Sie deren Einrichtungen, wann immer dies möglich ist. Verwenden Sie insbesondere STL-Zeichenfolgen und -Sammlungen, es sei denn, Sie haben einen sehr, sehr guten Grund, dies nicht zu tun. Lernen Sie auto_ptr und die Boost-Bibliothek für intelligente Zeiger sehr gut kennen, verstehen Sie, unter welchen Umständen jeder Typ von intelligenten Zeigern verwendet werden soll, und verwenden Sie dann intelligente Zeiger überall dort, wo Sie sonst möglicherweise Rohzeiger verwendet hätten. Ihr Code ist genauso effizient und weniger anfällig für Speicherlecks.

Verwenden Sie static_cast, dynamic_cast, const_cast und reinterpret_cast anstelle von C-Casts. Im Gegensatz zu Casts im C-Stil werden Sie wissen lassen, ob Sie wirklich nach einer anderen Art von Cast fragen, als Sie denken. Und sie fallen visuell auf und machen den Leser darauf aufmerksam, dass eine Besetzung stattfindet.

Avdi
quelle
8

Die Webseite C ++ Pitfalls von Scott Wheeler behandelt einige der wichtigsten C ++ - Fallstricke.

Funken
quelle
6

Zwei Fallstricke, von denen ich mir wünschte, ich hätte sie nicht auf die harte Tour gelernt:

(1) Viele Ausgaben (z. B. printf) werden standardmäßig gepuffert. Wenn Sie abstürzenden Code debuggen und gepufferte Debug-Anweisungen verwenden, ist die letzte Ausgabe, die Sie sehen, möglicherweise nicht die letzte im Code gefundene Druckanweisung. Die Lösung besteht darin, den Puffer nach jedem Debug-Druck zu leeren (oder die Pufferung ganz auszuschalten).

(2) Seien Sie vorsichtig mit Initialisierungen - (a) Vermeiden Sie Klasseninstanzen als Globals / Statics. und (b) versuchen Sie, alle Ihre Mitgliedsvariablen auf einen sicheren Wert in einem ctor zu initialisieren, selbst wenn es sich um einen trivialen Wert wie NULL für Zeiger handelt.

Begründung: Die Reihenfolge der globalen Objektinitialisierung kann nicht garantiert werden (Globale enthalten statische Variablen). Daher kann es vorkommen, dass Code nicht deterministisch fehlschlägt, da er davon abhängt, dass Objekt X vor Objekt Y initialisiert wird. Wenn Sie a nicht explizit initialisieren Bei Variablen vom primitiven Typ, z. B. einem Member-Bool oder einer Aufzählung einer Klasse, werden in überraschenden Situationen unterschiedliche Werte angezeigt. Auch hier kann das Verhalten sehr unbestimmt erscheinen.

Tyler
quelle
Die Lösung besteht nicht darin, mit Drucken zu debuggen
Dustin Getz
Manchmal ist dies die einzige Option ... zum Beispiel das Debuggen von Abstürzen, die nur im Release-Code und / oder auf einem anderen Zielarchitekten / einer anderen Plattform auftreten, auf der Sie sich gerade entwickeln.
Xan
3
Es gibt definitiv ausgefeiltere Möglichkeiten zum Debuggen. Die Verwendung von Ausdrucken hat sich jedoch bewährt und funktioniert an viel mehr Orten, als Sie möglicherweise auf einen netten Debugger zugreifen können. Ich bin nicht der Einzige, der das glaubt - siehe zum Beispiel Pike und Kernighans Programmierpraxis.
Tyler
+1 für die nichtdeterministische Initialisierung globaler Objekte. (Es gibt einige Regeln, aber sie sind nicht so intuitiv oder vollständig, wie wir
möchten
printf (und std :: cout) ist (sind) oft nur zeilengepuffert. Solange Sie relativ sicher sind, dass Sie nicht zwischen dem Starten von printf und dem Schlagen der neuen Zeile abstürzen, sollten Sie in Ordnung sein. Berücksichtigen Sie auch Compiler-Fehler, die die Erzeugung von Debug-Symbolen verhindern <grummeln murrt>
dash-tom-bang
6

Ich habe es bereits einige Male erwähnt, aber Scott Meyers 'Bücher Effective C ++ und Effective STL sind Gold wert, um mit C ++ zu helfen.

Wenn Sie sich das vorstellen, ist Steven Dewhursts C ++ Gotchas auch eine ausgezeichnete Ressource "aus den Gräben". Sein Artikel über das Rollen Ihrer eigenen Ausnahmen und wie sie aufgebaut sein sollten, hat mir in einem Projekt wirklich geholfen.

Rob Wells
quelle
6

Verwenden von C ++ wie C. Einen Erstellungs- und Freigabezyklus im Code haben.

In C ++ ist dies nicht ausnahmesicher und daher wird die Version möglicherweise nicht ausgeführt. In C ++ verwenden wir RAII , um dieses Problem zu lösen.

Alle Ressourcen, die manuell erstellt und freigegeben wurden, sollten in ein Objekt eingeschlossen werden, damit diese Aktionen im Konstruktor / Destruktor ausgeführt werden.

// C Code
void myFunc()
{
    Plop*   plop = createMyPlopResource();

    // Use the plop

    releaseMyPlopResource(plop);
}

In C ++ sollte dies in ein Objekt eingeschlossen werden:

// C++
class PlopResource
{
    public:
        PlopResource()
        {
            mPlop=createMyPlopResource();
            // handle exceptions and errors.
        }
        ~PlopResource()
        {
             releaseMyPlopResource(mPlop);
        }
    private:
        Plop*  mPlop;
 };

void myFunc()
{
    PlopResource  plop;

    // Use the plop
    // Exception safe release on exit.
}
Loki Astari
quelle
Ich bin mir nicht sicher, ob wir es hinzufügen sollen. aber vielleicht sollten wir es nicht kopierbar / nicht zuweisbar machen?
Johannes Schaub - Litb
4

Das Buch C ++ Gotchas kann sich als nützlich erweisen.

Peter Mortensen
quelle
4

Hier sind ein paar Gruben, in die ich das Unglück hatte zu fallen. All dies hat gute Gründe, die ich erst verstanden habe, nachdem ich von einem Verhalten gebissen wurde, das mich überraschte.

  • virtualFunktionen in Konstruktoren sind nicht .

  • Verstoßen Sie nicht gegen die ODR (One Definition Rule) , dafür sind unter anderem anonyme Namespaces gedacht.

  • Die Reihenfolge der Initialisierung der Mitglieder hängt von der Reihenfolge ab, in der sie deklariert werden.

    class bar {
        vector<int> vec_;
        unsigned size_; // Note size_ declared *after* vec_
    public:
        bar(unsigned size)
            : size_(size)
            , vec_(size_) // size_ is uninitialized
            {}
    };
    
  • Standardwerte und virtualunterschiedliche Semantik.

    class base {
    public:
        virtual foo(int i = 42) { cout << "base " << i; }
    };
    
    class derived : public base {
    public:
        virtual foo(int i = 12) { cout << "derived "<< i; }
    };
    
    derived d;
    base& b = d;
    b.foo(); // Outputs `derived 42`
    
Motti
quelle
1
Das letzte ist eine knifflige! Autsch!
j_random_hacker
C # macht dasselbe (wie der virtuelle / Standardwert eins), jetzt, wo C # 4 Standardwerte hat.
BlueRaja - Danny Pflughoeft
3

Die wichtigsten Fallstricke für Anfänger sind die Vermeidung von Verwechslungen zwischen C und C ++. C ++ sollte niemals als bloß besseres C oder C mit Klassen behandelt werden, da dies seine Leistung einschränkt und es sogar gefährlich machen kann (insbesondere bei Verwendung von Speicher wie in C).

Konrad Rudolph
quelle
3

Schauen Sie sich boost.org an . Es bietet viele zusätzliche Funktionen, insbesondere die Implementierung von Smart Pointern.

Paul Whitehurst
quelle
3
  1. C ++ FAQ Lite nicht lesen . Es erklärt viele schlechte (und gute!) Praktiken.
  2. Boost nicht verwenden . Sie sparen sich viel Frust, wenn Sie Boost nach Möglichkeit nutzen.
Teratorn
quelle
2

Seien Sie vorsichtig, wenn Sie intelligente Zeiger und Containerklassen verwenden.

registrierter Nutzer
quelle
Frage zur Antwort: Was ist falsch daran, intelligente Zeiger mit Containerklassen zu verwenden? Beispiel: vector <shared_ptr <int>>. Können Sie das näher erläutern?
Aaron
2
er bezieht sich auf Container von auto_ptr, die verboten sind, aber manchmal kompiliert werden
Dustin Getz
@Aaron: Insbesondere zerstört der Zuweisungsoperator von auto_ptr seinen Quelloperanden, was bedeutet, dass er nicht mit Standardcontainern verwendet werden kann, die darauf angewiesen sind, dass dies nicht geschieht. shared_ptr ist jedoch in Ordnung.
j_random_hacker
2

Vermeiden Sie Pseudoklassen und Quasi-Klassen ... Überdesign grundsätzlich.

Epatel
quelle
Ich arbeite derzeit an einem solchen Projekt mit (n) Quasiklassen. Wir brauchen mehr Bewusstsein für dieses Anti-Muster!
DarenW
2

Vergessen, einen virtuellen Basisklassendestruktor zu definieren. Dies bedeutet, dass das Aufrufen deleteeiner Basis * den abgeleiteten Teil nicht zerstört.

xtofl
quelle
1

Halten Sie die Namensräume gerade (einschließlich Struktur, Klasse, Namespace und Verwendung). Das ist meine größte Frustration, wenn das Programm einfach nicht kompiliert wird.

David Thornley
quelle
1

Verwenden Sie zum Durcheinander häufig gerade Zeiger. Verwenden Sie stattdessen RAII für fast alles und stellen Sie sicher, dass Sie die richtigen intelligenten Zeiger verwenden. Wenn Sie irgendwo außerhalb eines Handles oder einer Zeigerklasse "delete" schreiben, machen Sie es höchstwahrscheinlich falsch.

David Thornley
quelle
1
  • Blizpasta. Das ist eine riesige, die ich oft sehe ...

  • Nicht initialisierte Variablen sind ein großer Fehler, den meine Schüler machen. Viele Java-Leute vergessen, dass nur das Sagen von "int counter" den Zähler nicht auf 0 setzt. Da Sie Variablen in der h-Datei definieren (und sie im Konstruktor / Setup eines Objekts initialisieren müssen), ist es leicht zu vergessen.

  • Off-by-One-Fehler bei forSchleifen / Array-Zugriff.

  • Objektcode wird beim Start von Voodoo nicht ordnungsgemäß gereinigt.

Zach
quelle
1
  • static_cast Downcast auf einer virtuellen Basisklasse

Nicht wirklich ... Nun zu meinem Missverständnis: Ich dachte, dass Aim Folgenden eine virtuelle Basisklasse ist, obwohl dies tatsächlich nicht der Fall ist. es ist gemäß 10.3.1 eine polymorphe Klasse . Die Verwendung static_casthier scheint in Ordnung zu sein.

struct B { virtual ~B() {} };

struct D : B { };

Zusammenfassend ist dies eine gefährliche Falle.

Konrad Rudolph
quelle
siehe meine erweiterte Frage oben
Johannes Schaub - litb
0

Überprüfen Sie immer einen Zeiger, bevor Sie ihn dereferenzieren. In C können Sie normalerweise mit einem Absturz an der Stelle rechnen, an der Sie einen schlechten Zeiger dereferenzieren. In C ++ können Sie eine ungültige Referenz erstellen, die an einer Stelle abstürzt, die weit von der Ursache des Problems entfernt ist.

class SomeClass
{
    ...
    void DoSomething()
    {
        ++counter;    // crash here!
    }
    int counter;
};

void Foo(SomeClass & ref)
{
    ...
    ref.DoSomething();    // if DoSomething is virtual, you might crash here
    ...
}

void Bar(SomeClass * ptr)
{
    Foo(*ptr);    // if ptr is NULL, you have created an invalid reference
                  // which probably WILL NOT crash here
}
Mark Ransom
quelle
Das Überprüfen auf NULL hilft nicht viel. Ein Zeiger kann einen Wert ungleich Null haben und dennoch auf ein gelöschtes oder anderweitig ungültiges Objekt verweisen.
Nemanja Trifunovic
Stimmt, aber meiner Erfahrung nach ist ein NULL-Zeiger häufiger als die anderen Arten ungültiger Zeiger. Vielleicht liegt das daran, dass ich es mir zur Gewohnheit mache, meine Zeiger nach dem Löschen zu löschen.
Mark Ransom
Dies ist Teil Ihrer Fehlerbehandlungsstrategie. Ich würde sagen, vermeiden Sie das Einchecken von NULL-Zeigern im Kerncode (eher behaupten), aber garantieren Sie, dass Sie keine ungültigen Werte übergeben (Design by Contract).
xtofl
0

Vergessen eines & und damit Erstellen einer Kopie anstelle einer Referenz.

Das ist mir zweimal auf unterschiedliche Weise passiert:

  • Eine Instanz befand sich in einer Argumentliste, die dazu führte, dass ein großes Objekt auf den Stapel gelegt wurde, was zu einem Stapelüberlauf und einem Absturz des eingebetteten Systems führte.

  • Ich habe die &Variable für eine Instanz vergessen , mit dem Effekt, dass das Objekt kopiert wurde. Nachdem ich mich als Listener für die Kopie registriert hatte, fragte ich mich, warum ich nie die Rückrufe vom Originalobjekt erhalten hatte.

Beide waren ziemlich schwer zu erkennen, da der Unterschied gering und schwer zu erkennen ist, und ansonsten werden Objekte und Referenzen syntaktisch auf die gleiche Weise verwendet.

Sternenblau
quelle
0

Absicht ist (x == 10):

if (x = 10) {
    //Do something
}

Ich dachte, ich würde diesen Fehler niemals selbst machen, aber ich habe es kürzlich tatsächlich getan.

Blizpasta
quelle
3
So ziemlich jeder Compiler wird heutzutage eine Warnung dafür ausgeben
Adam Rosenfield
Wenn Sie eine Konstante == für eine Variable verwenden, können Sie diese Fehler
besser erkennen.
Das hilft nicht, wenn Sie beabsichtigt habenif (x == y)
dan04
0

Der Aufsatz / Artikel Zeiger, Referenzen und Werte ist sehr nützlich. Es geht darum, Fallstricke und bewährte Verfahren zu vermeiden. Sie können auch die gesamte Site durchsuchen, die Programmiertipps enthält, hauptsächlich für C ++.

lauert
quelle
0

Ich habe viele Jahre mit der C ++ - Entwicklung verbracht. Ich habe eine kurze Zusammenfassung der Probleme geschrieben, die ich vor Jahren damit hatte. Standardkonforme Compiler sind kein Problem mehr, aber ich vermute, dass die anderen beschriebenen Fallstricke weiterhin gültig sind.

Todd Stout
quelle
-1
#include <boost/shared_ptr.hpp>
class A {
public:
  void nuke() {
     boost::shared_ptr<A> (this);
  }
};

int main(int argc, char** argv) {
  A a;
  a.nuke();
  return(0);
}
gsarkis
quelle
3
Die Unfähigkeit, boost :: shared_ptr zu verwenden, ist kaum eine Gefahr für die Sprache.
0xC0DEFACE
+1. Obwohl in den shared_ptr-Dokumenten angegeben ist, dass diese Verwendung nicht unterstützt wird (und eine Problemumgehung bietet, enable_shared_from_this), ist dies ein häufiger Anwendungsfall, und es ist nicht sofort offensichtlich, dass der obige Code fehlschlägt. Es scheint sogar nach der Regel zu spielen, dass "ein roher Zeiger sofort in ein shared_ptr eingeschlossen wird". Eine echte Falle IMHO.
j_random_hacker