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.org
im 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
, delete
weiternew[]
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 delete
was 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 delete
oder delete[]
auf einen bereits gelöschten Zeiger
Lösung : Weisen Sie jedem gelöschten Zeiger 0 zu. Ein Aufruf delete
oder 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_ptr
oder std::shared_ptr
wie in anderen Antworten angegeben.
Ändern eines Objekts zweimal wie in diesem Beispiel: i = ++i;
Lösung : Das obige sollte i
dem Wert von zugewiesen werden i+1
. Aber was es tut, ist nicht definiert. Anstatt i
das Ergebnis zu erhöhen und zuzuweisen, ändert es sich auch i
auf der rechten Seite. Das Ändern eines Objekts zwischen zwei Sequenzpunkten ist ein undefiniertes Verhalten. Sequenzpunkte umfassen ||
, &&
, comma-operator
, semicolon
und 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::endl
anstelle \n
oder 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 t
zurückkehrt Type
und einen Parameter des Typs aufweist , other_type
die aufgerufen wird value
. Sie lösen es, indem Sie das erste Argument in Klammern setzen. Jetzt erhalten Sie eine Variable t
vom Typ Type
:
Type t((other_type(value)))
Aufruf der Funktion eines freien Objekts, das nur in der aktuellen Übersetzungseinheit ( .cpp
Datei) 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 .cpp
Datei, während sie in einer anderen .cpp
Datei 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;
*pcc = &c;
*pc = ’C’;
}
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 .