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?
Antworten:
Eine kurze Liste könnte sein:
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:
Das Lesen dieser Bücher hat mir mehr als alles andere geholfen, die Art von Fallstricken zu vermeiden, nach denen Sie fragen.
quelle
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++
aufirc.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.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 dunew[]
unddelete
was dunew
, dann wird das nicht passieren.Ausnahme :
typedef T type[N]; T * pT = new type; delete[] pT;
Sie müssen,
delete[]
obwohl Sienew
, da Sie ein Array neu gemacht haben. Wenn Sie also mit arbeitentypedef
, seien Sie besonders vorsichtig.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.
Lösung : Weisen Sie jedem gelöschten Zeiger 0 zu. Ein Aufruf
delete
oderdelete[]
ein Nullzeiger bewirkt nichts.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.
Lösung : Hier erfahren Sie, warum sie unterschiedlich sind und wie Sie damit umgehen.
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.Lösung : Verwenden Sie intelligente Zeiger wie
std::unique_ptr
oderstd::shared_ptr
wie in anderen Antworten angegeben.Lösung : Das obige sollte
i
dem Wert von zugewiesen werdeni+1
. Aber was es tut, ist nicht definiert. Anstatti
das Ergebnis zu erhöhen und zuzuweisen, ändert es sich auchi
auf der rechten Seite. Das Ändern eines Objekts zwischen zwei Sequenzpunkten ist ein undefiniertes Verhalten. Sequenzpunkte umfassen||
,&&
,comma-operator
,semicolon
undentering a function
(nicht erschöpfende Liste!). Ändern Sie den Code wie folgt, damit er sich korrekt verhält:i = i + 1;
Verschiedene Probleme
Lösung : Leeren Sie den Stream, indem Sie ihn entweder
std::endl
anstelle\n
oder durch Aufrufen streamenstream.flush();
.Lösung : Das Problem tritt auf, weil der Compiler beispielsweise interpretiert
Type t(other_type(value));
als Funktion Erklärung einer Funktion
t
zurückkehrtType
und einen Parameter des Typs aufweist ,other_type
die aufgerufen wirdvalue
. Sie lösen es, indem Sie das erste Argument in Klammern setzen. Jetzt erhalten Sie eine Variablet
vom TypType
:Type t((other_type(value)));
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.
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.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.
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.
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öchtenT**
: 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 .
quelle
Einige müssen über C ++ - Bücher verfügen, mit denen Sie häufige C ++ - Fallstricke vermeiden können:
Effektives C ++
Effektiveres C ++
Effektives STL
Das effektive STL-Buch erklärt das Problem des Vektors der Bools :)
quelle
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)."
quelle
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.
quelle
Die Webseite C ++ Pitfalls von Scott Wheeler behandelt einige der wichtigsten C ++ - Fallstricke.
quelle
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.
quelle
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.
quelle
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. }
quelle
Das Buch C ++ Gotchas kann sich als nützlich erweisen.
quelle
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.
virtual
Funktionen 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
virtual
unterschiedliche 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`
quelle
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).
quelle
Schauen Sie sich boost.org an . Es bietet viele zusätzliche Funktionen, insbesondere die Implementierung von Smart Pointern.
quelle
PRQA verfügt über einen hervorragenden und kostenlosen C ++ - Codierungsstandard, der auf Büchern von Scott Meyers, Bjarne Stroustrop und Herb Sutter basiert. Alle diese Informationen werden in einem Dokument zusammengefasst.
quelle
quelle
Seien Sie vorsichtig, wenn Sie intelligente Zeiger und Containerklassen verwenden.
quelle
Vermeiden Sie Pseudoklassen und Quasi-Klassen ... Überdesign grundsätzlich.
quelle
Vergessen, einen virtuellen Basisklassendestruktor zu definieren. Dies bedeutet, dass das Aufrufen
delete
einer Basis * den abgeleiteten Teil nicht zerstört.quelle
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.
quelle
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.
quelle
Lesen Sie das Buch C ++ Gotchas: Vermeiden häufiger Probleme beim Codieren und Entwerfen .
quelle
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
for
Schleifen / Array-Zugriff.Objektcode wird beim Start von Voodoo nicht ordnungsgemäß gereinigt.
quelle
Nicht wirklich ... Nun zu meinem Missverständnis: Ich dachte, dass
A
im Folgenden eine virtuelle Basisklasse ist, obwohl dies tatsächlich nicht der Fall ist. es ist gemäß 10.3.1 eine polymorphe Klasse . Die Verwendungstatic_cast
hier scheint in Ordnung zu sein.struct B { virtual ~B() {} }; struct D : B { };
Zusammenfassend ist dies eine gefährliche Falle.
quelle
Ü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 }
quelle
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.
quelle
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.
quelle
if (x == y)
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 ++.
quelle
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.
quelle
#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); }
quelle