Ich habe irgendwo gelesen, dass bei der Verwendung von C ++ empfohlen wird, keine Zeiger zu verwenden. Warum sind Zeiger eine so schlechte Idee, wenn Sie C ++ verwenden. Was ist die bessere Alternative und Vorgehensweise in C ++ für C-Programmierer, die an die Verwendung von Zeigern gewöhnt sind?
45
NetConnection
Instanzen vom Server getrennt werden ( stackoverflow.com/questions/14780456/… ), und es gibt ein Problem mit mehreren Objekten in einem Programm, die es ausdrücklich ablehnt, jemals zu sammeln ...GCRoots are never garbage collected.
und der Absatz beginnt mitThe MMgc is considered a conservative collector for mark/sweep.
). Technisch ist dies ein Problem in Adobe Virtual Machine 2, nicht in AS3 selbst. Wenn Sie jedoch Probleme in höheren Sprachen haben, in denen die Garbage Collection im Wesentlichen integriert ist, haben Sie in der Sprache häufig keine echte Möglichkeit zum Debuggen diese Fragen komplett aus dem Programm. ...Antworten:
Ich denke, sie bedeuten, dass Sie intelligente Zeiger anstelle von regulären Zeigern verwenden sollten.
In C ++ liegt der Schwerpunkt auf der Garbage Collection und der Vermeidung von Speicherverlusten (um nur zwei zu nennen). Zeiger sind ein grundlegender Bestandteil der Sprache, daher ist es so gut wie unmöglich, sie nicht zu verwenden, es sei denn, es handelt sich um die trivialsten Programme.
quelle
Da ich derjenige bin, der die Polemik "benutze keine f * kking-Zeiger" veröffentlicht hat, denke ich, dass ich hier einen Kommentar abgeben sollte.
Als Polemik ist es offensichtlich ein extremer Gesichtspunkt. Es gibt definitiv legitime Verwendungen von (rohen) Zeigern. Aber ich (und viele professionelle C ++ - Programmierer) behaupten, dass diese Fälle äußerst selten sind. Aber was wir wirklich meinen, ist das Folgende:
Zuerst:
Hier bedeutet "eigenes Gedächtnis" im Wesentlichen, dass irgendwann
delete
auf diesen Zeiger zugegriffen wird (aber es ist allgemeiner als das). Diese Aussage kann mit Sicherheit als absolut angesehen werden. Die einzige Ausnahme ist die Implementierung eines eigenen Smart Pointers (oder einer anderen Speicherverwaltungsstrategie). Und selbst dort sollten Sie normalerweise noch einen Smart Pointer auf niedriger Stufe verwenden.Die Begründung hierfür ist ganz einfach: Rohe Zeiger, die über Speicher verfügen, führen zu einer Fehlerquelle. Und diese Fehler sind in bestehender Software sehr häufig: Speicherverluste und doppeltes Löschen - beides eine direkte Folge von unklarem Ressourcenbesitz (aber in entgegengesetzter Richtung).
Dieses Problem kann praktisch kostenlos vollständig beseitigt werden, indem einfach intelligente Zeiger anstelle von unformatierten Zeigern verwendet werden (Vorsicht: Dies erfordert natürlich noch Nachdenken; gemeinsame Zeiger können zu Zyklen und damit wiederum zu Speicherverlusten führen - dies ist jedoch einfach vermeidbar).
Zweite:
Im Gegensatz zu anderen Sprachen unterstützt C ++ die Wertesemantik sehr stark und benötigt einfach keine Indirektion von Zeigern. Dies wurde nicht sofort erkannt - in der Vergangenheit wurde C ++ erfunden, um eine einfache Objektorientierung in C zu ermöglichen, und es wurde stark darauf geachtet, Objektgraphen zu konstruieren, die durch Zeiger verbunden waren. In modernem C ++ ist dieses Paradigma jedoch selten die beste Wahl, und moderne C ++ - Redewendungen benötigen häufig überhaupt keine Zeiger . Sie arbeiten eher mit Werten als mit Zeigern.
Leider hat sich diese Nachricht in weiten Teilen der C ++ - Benutzergemeinschaft noch nicht durchgesetzt. Infolgedessen ist der größte Teil des geschriebenen C ++ - Codes immer noch mit überflüssigen Zeigern übersät, die den Code komplex, langsam und fehlerhaft / unzuverlässig machen.
Für jemanden, der modernen C ++ kennt, dann ist es klar , dass man sehr selten brauchen keine Zeiger (entweder intelligent oder roh, außer wenn sie als Iteratoren verwenden). Der resultierende Code ist kürzer, weniger komplex, besser lesbar, oft effizienter und zuverlässiger.
quelle
std::unique_ptr
. Warum auch nichtptr_vec
? In der Regel wird ein Wertevektor mit dennoch schneller ausgetauscht (insbesondere mit Bewegungssemantik).boost::variant
mit arecursive_wrapper
ist wohl meine lieblingslösung um eine DAG darzustellen.Ganz einfach, weil Ihnen Abstraktionen zur Verfügung stehen, die die eher temperamentvollen Aspekte der Verwendung von Zeigern verbergen, z. B. den Zugriff auf den unformatierten Speicher und das Aufräumen nach Ihren Zuweisungen. Mit intelligenten Zeigern, Containerklassen und Entwurfsmustern wie RAII wird der Bedarf für die Verwendung von rohen Zeigern verringert. Das heißt, wie bei jeder Abstraktion sollten Sie verstehen, wie sie tatsächlich funktionieren, bevor Sie darüber hinausgehen.
quelle
Relativ einfach ausgedrückt lautet die C-Mentalität "Haben Sie ein Problem? Verwenden Sie einen Zeiger". Sie können dies in C-Strings, Funktionszeigern, Zeigern als Iteratoren, Zeigern auf Zeiger und ungültigen Zeigern sehen - selbst in den frühen Tagen von C ++ mit Elementzeigern.
In C ++ können Sie jedoch Werte für viele oder alle dieser Aufgaben verwenden. Benötigen Sie eine Funktionsabstraktion?
std::function
. Es ist ein Wert, der eine Funktion ist.std::string
? Es ist ein Wert, das ist eine Zeichenfolge. Sie können ähnliche Ansätze in C ++ beobachten. Dies erleichtert die Analyse des Codes sowohl für den Menschen als auch für den Compiler erheblich.quelle
Einer der Gründe ist die zu weite Anwendung von Zeigern. Sie können zum Durchlaufen von Containern verwendet werden, um zu vermeiden, dass große Objekte beim Übergeben an eine Funktion kopiert werden, nicht triviale Lebensdauerverwaltung, Zugriff auf zufällige Speicherbereiche usw. Sobald Sie sie für einen Zweck verwendet haben, stehen andere Funktionen zur Verfügung sofort selbständig auf Vorsatz.
Die Auswahl eines Tools für den genauen Zweck macht Code einfacher und bewusster sichtbar - Iteratoren für Iterationen, intelligente Zeiger für die Lebensdauerverwaltung usw.
quelle
Neben den bereits aufgeführten Gründen gibt es eine offensichtliche: bessere Optimierungen. Die Aliasing-Analyse ist in Gegenwart einer Zeigerarithmetik viel zu kompliziert, wohingegen Referenzen auf einen Optimierer hinweisen, sodass eine viel tiefere Aliasing-Analyse möglich ist, wenn nur Referenzen verwendet werden.
quelle
Neben dem von @jmquigley angegebenen Risiko von Speicherlecks kann die Zeiger- und Zeigerarithmetik als problematisch angesehen werden, da Zeiger überall im Speicher hinweisen können und "schwer zu findende Fehler" und "Sicherheitslücken" verursachen.
Aus diesem Grund wurden sie in C # und Java fast aufgegeben.
quelle
unsafe
SchlüsselwortC ++ unterstützt die meisten Funktionen von C sowie Objekte und Klassen. C hatte schon Zeiger und andere Sachen.
Zeiger sind eine sehr nützliche Technik, die mit der Objektorientierung kombiniert werden kann, und C ++ unterstützt sie. Diese Technik ist jedoch schwer zu lehren und zu verstehen, und es ist sehr leicht, unerwünschte Fehler zu verursachen.
Viele neue Programmiersprachen geben vor, keine Zeiger für Objekte zu verwenden, wie Java, .NET, Delphi, Vala, PHP, Scala. Es werden jedoch weiterhin Zeiger "hinter den Kulissen" verwendet. Diese "versteckten Zeigertechniken" werden "Referenzen" genannt.
Wie auch immer, ich betrachte Zeiger als Programmiermuster, als einen gültigen Weg, um bestimmte Probleme zu lösen, so wie es die objektorientierte Programmierung tut.
Andere Entwickler sind möglicherweise anderer Meinung. Ich schlage jedoch vor, dass Studenten und Programmierer lernen, wie man:
(1) Verwenden Sie Zeiger ohne Objekte
(2) Objekte ohne Zeiger
(3) explizite Zeiger auf Objekte
(4) "versteckte" Zeiger auf Objekte (AKA- Referenz ) ;-)
In dieser Reihenfolge.
Auch wenn es schwer zu lehren und schwer zu lernen ist. Object Pascal (Delphi, FreePascal, andere) und
C++
(nicht Java oder C #) können für diese Ziele verwendet werden.Und später können unerfahrene Programmierer Programmiersprachen wie Java, C #, objektorientiertes PHP und andere "versteckte Zeiger auf Objekte" verwenden.
quelle
Apropos VC6: Wenn Sie einen Zeiger einer Klasse (die Sie instanziieren) in eine Variable (z. B. DWORD) umwandeln, können Sie, auch wenn dieser Zeiger lokal ist, über alle Funktionen, die denselben Heap verwenden, auf die Klasse zugreifen. Die instanziierte Klasse ist als lokal definiert, ist es jedoch nicht. Soweit ich weiß, ist jede Adresse einer Heap-Variablen, -Struktur oder -Klasse während des gesamten Lebens der Hosting-Klasse eindeutig.
Beispiel:
EDIT Das ist ein sehr kleiner Teil des Originalcodes. Die CSRecodset-Klasse ist nur eine Casting-Klasse von CXdbRecordset, in der der gesamte echte Code enthalten ist. Auf diese Weise kann der Benutzer das, was ich geschrieben habe, nutzen, ohne meine Rechte zu verlieren. Ich behaupte nicht, dass meine Datenbank-Engine professionell ist, aber es funktioniert wirklich.
EDIT: beantragt von DeadMG:
quelle
DWORD
Umwandlung in ist missbräuchlich und möglicherweise falsch (DWORD ist nicht unbedingt breit genug, um einen Zeiger aufzunehmen). Wenn Sie einen untypisierten Zeiger benötigen, verwenden Sievoid*
-, aber wenn Sie feststellen, dass Sie diesen in C ++ benötigen, haben Sie häufig ein Designproblem in Ihrem Code, das Sie beheben sollten.