wake-up-neo.com

Warum verwenden C ++ - Bibliotheken und Frameworks niemals intelligente Zeiger?

In einigen Artikeln habe ich gelesen, dass rohe Zeiger so gut wie nie verwendet werden sollten. Stattdessen sollten sie immer in intelligente Zeiger eingeschlossen werden, unabhängig davon, ob es sich um bereichsspezifische oder gemeinsam genutzte Zeiger handelt.

Mir ist jedoch aufgefallen, dass Frameworks wie Qt, wxWidgets und Bibliotheken wie Boost niemals intelligente Zeiger zurückgeben oder erwarten, als würden sie diese überhaupt nicht verwenden. Stattdessen geben sie unformatierte Zeiger zurück oder erwarten sie. Gibt es einen Grund dafür? Sollte ich mich von intelligenten Zeigern fernhalten, wenn ich eine öffentliche API schreibe, und warum?

Ich frage mich nur, warum intelligente Zeiger empfohlen werden, wenn viele Großprojekte sie zu vermeiden scheinen.

156
laurent

Abgesehen von der Tatsache, dass viele Bibliotheken vor dem Aufkommen von Standard-Smart-Pointern geschrieben wurden, ist der Hauptgrund wahrscheinlich das Fehlen eines Standard-C++ Application Binary Interface (ABI).

Wenn Sie eine Nur-Header-Bibliothek schreiben, können Sie intelligente Zeiger und Standardcontainer nach Herzenslust weitergeben. Ihre Quelle steht Ihrer Bibliothek zum Zeitpunkt der Kompilierung zur Verfügung. Sie verlassen sich also ausschließlich auf die Stabilität ihrer Schnittstellen und nicht auf ihre Implementierungen.

Aufgrund des Fehlens von Standard-ABI können Sie diese Objekte jedoch im Allgemeinen nicht sicher über Modulgrenzen hinweg übergeben. Ein GCC shared_ptr Unterscheidet sich wahrscheinlich von einem MSVC shared_ptr, Der sich auch von einem Intel shared_ptr Unterscheiden kann. Selbst mit dem Compiler same kann nicht garantiert werden, dass diese Klassen zwischen den Versionen binär kompatibel sind.

Wenn Sie eine vorgefertigte Version Ihrer Bibliothek verteilen möchten, benötigen Sie ein Standard-ABI, auf das Sie sich verlassen können. C hat keine, aber Compiler-Anbieter sind sehr gut in Bezug auf die Interoperabilität zwischen C-Bibliotheken für eine bestimmte Plattform - es gibt de facto Standards.

Die Situation ist nicht so gut für C++. Einzelne Compiler können die Interoperation zwischen ihren eigenen Binärdateien verwalten, sodass Sie die Möglichkeit haben, für jeden unterstützten Compiler, häufig GCC und MSVC, eine Version zu verteilen. Vor diesem Hintergrund exportieren die meisten Bibliotheken nur eine C-Schnittstelle - und das bedeutet unformatierte Zeiger.

Nicht-Bibliotheks-Code sollte jedoch generell Smart Pointer gegenüber Raw bevorzugen.

123
Jon Purdy

Es kann viele Gründe geben. Um einige von ihnen aufzulisten:

  1. Intelligente Zeiger sind erst seit kurzem Standard. Bis dahin waren sie Teil anderer Bibliotheken
  2. Ihr Hauptzweck besteht darin, Speicherlecks zu vermeiden. Viele Bibliotheken haben keine eigene Speicherverwaltung. Im Allgemeinen bieten sie Dienstprogramme und APIs
  3. Sie werden als Wrapper implementiert, da sie tatsächlich Objekte und keine Zeiger sind. Was im Vergleich zu rohen Zeigern zusätzliche Zeit-/Raumkosten verursacht; Die Benutzer der Bibliotheken möchten möglicherweise keinen solchen Overhead haben

Bearbeiten: Die Verwendung von intelligenten Zeigern ist die Entscheidung eines Entwicklers. Das hängt von verschiedenen Faktoren ab.

  1. In leistungskritischen Systemen möchten Sie möglicherweise keine intelligenten Zeiger verwenden, die Overhead erzeugen

  2. Für das Projekt, für das die Abwärtskompatibilität erforderlich ist, möchten Sie möglicherweise keine intelligenten Zeiger verwenden, die C++ 11-spezifische Funktionen aufweisen

Edit2 Es gibt innerhalb von 24 Stunden eine Folge von mehreren Abstimmungen, weil die Passage darunter liegt. Ich verstehe nicht, warum die Antwort herabgestuft wird, obwohl es sich im Folgenden nur um einen zusätzlichen Vorschlag und nicht um eine Antwort handelt.
Mit C++ haben Sie jedoch immer die Möglichkeit, die Optionen offen zu halten. :) z.B.

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};

Und in Ihrem Code verwenden Sie es als:

Pointer<int>::type p;

Für diejenigen, die sagen, dass ein intelligenter Zeiger und ein roher Zeiger unterschiedlich sind, stimme ich dem zu. Der obige Code war nur eine Idee wo man einen Code schreiben kann, der nur mit einem #define Austauschbar ist, ist dies kein Zwang;

Beispielsweise muss T* Explizit gelöscht werden, ein Smart Pointer jedoch nicht. Wir können eine Vorlage Destroy() haben, um damit umzugehen.

template<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

und benutze es als:

Destroy(p);

Auf die gleiche Weise können wir einen rohen Zeiger direkt kopieren und für einen intelligenten Zeiger eine spezielle Operation verwenden.

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));

Wobei Assign() wie folgt ist:

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}
40
iammilind

Es gibt zwei Probleme mit intelligenten Zeigern (vor C++ 11):

  • nicht-Standards, daher neigt jede Bibliothek dazu, ihre eigenen neu zu erfinden (NIH-Syndrom und Probleme mit Abhängigkeiten)
  • mögliche Kosten

Der Standard Smart Pointer ist, da er kostenlos ist, unique_ptr. Leider ist eine C++ 11-Verschiebungssemantik erforderlich, die erst kürzlich aufgetaucht ist. Alle anderen intelligenten Zeiger kosten (shared_ptr, intrusive_ptr) oder haben weniger als ideale Semantik (auto_ptr).

Mit C++ 11 um die Ecke, bringt ein std::unique_ptr, man wäre versucht zu denken, dass es endlich vorbei ist ... Ich bin nicht so optimistisch.

Nur wenige große Compiler implementieren den größten Teil von C++ 11 und dies nur in ihren aktuellen Versionen. Wir können davon ausgehen, dass große Bibliotheken wie QT und Boost bereit sind, die Kompatibilität mit C++ 03 für eine Weile beizubehalten, was die weite Verbreitung der neuen und glänzenden intelligenten Zeiger einigermaßen ausschließt.

35
Matthieu M.

Sie sollten sich nicht von intelligenten Zeigern fernhalten, da diese vor allem in Anwendungen zum Einsatz kommen, in denen Sie ein Objekt herumreichen müssen.

Bibliotheken geben entweder nur einen Wert zurück oder füllen ein Objekt. Sie haben normalerweise keine Objekte, die an vielen Orten verwendet werden müssen, sodass sie keine intelligenten Zeiger verwenden müssen (zumindest nicht in ihrer Benutzeroberfläche, sie können sie intern verwenden).

Ich könnte als Beispiel eine Bibliothek nehmen, an der wir gearbeitet haben. Nach einigen Monaten der Entwicklung wurde mir klar, dass wir in einigen Klassen nur Zeiger und intelligente Zeiger verwendeten (3-5% aller Klassen).

Die Übergabe von Variablen als Referenz war an den meisten Stellen ausreichend. Wir verwendeten intelligente Zeiger, wenn wir ein Objekt hatten, das null sein konnte, und rohe Zeiger, wenn eine Bibliothek, die wir verwendeten, uns dazu zwang.

Bearbeiten (Ich kann wegen meines Rufs keinen Kommentar abgeben): Das Übergeben von Variablen als Referenz ist sehr flexibel: Wenn Sie möchten, dass das Objekt nur lesbar ist, können Sie eine konstante Referenz verwenden (Sie können immer noch einige böse Casts durchführen um das Objekt schreiben zu können), aber Sie erhalten den größtmöglichen Schutz (das gleiche gilt für intelligente Zeiger). Aber ich bin damit einverstanden, dass es viel schöner ist, das Objekt einfach zurückzugeben.

12
Robot Mess

Qt hat viele Teile der Standard-Bibliothek sinnlos neu erfunden, um Java zu werden. Ich glaube, dass es tatsächlich seine eigenen intelligenten Zeiger hat, aber im Allgemeinen ist es kaum ein Höhepunkt des Designs. wxWidgets wurde meines Wissens lange vor dem Schreiben brauchbarer Smart Pointer entwickelt.

Was Boost angeht, gehe ich davon aus, dass sie, wo immer dies angebracht ist, intelligente Zeiger verwenden. Möglicherweise müssen Sie genauer sein.

Vergessen Sie außerdem nicht, dass intelligente Zeiger vorhanden sind, um die Eigentümerschaft durchzusetzen. Wenn die API keine Besitzersemantik hat, warum dann einen intelligenten Zeiger verwenden?

9
Puppy

Gute Frage. Ich kenne die Artikel nicht, auf die Sie sich beziehen, aber ich habe von Zeit zu Zeit ähnliche Dinge gelesen. Mein Verdacht ist, dass die Autoren solcher Artikel tendenziell eine Tendenz gegen C++ - Programmierung hegen. Wenn der Schreiber nur dann in C++ programmiert, wenn er es muss, dann kehrt er zu Java oder so bald wie möglich zurück, dann teilt er die C++ - Denkweise nicht wirklich.

Man vermutet, dass einige oder die meisten der gleichen Autoren Speicher-Manager bevorzugen, die Müll sammeln. Ich nicht, aber ich denke anders als sie.

Intelligente Zeiger sind großartig, aber sie müssen Referenzzählungen beibehalten. Die Führung von Referenzzählungen verursacht zur Laufzeit Kosten - oft bescheidene Kosten, aber dennoch Kosten. Es ist nichts Falsches daran, diese Kosten durch Verwendung von bloßen Zeigern zu sparen, insbesondere wenn die Zeiger von Destruktoren verwaltet werden.

Eine der herausragenden Eigenschaften von C++ ist die Unterstützung für die Programmierung eingebetteter Systeme. Die Verwendung von bloßen Zeigern ist ein Teil davon.

pdate: Ein Kommentator hat richtig bemerkt, dass C++ 's neues unique_ptr (verfügbar seit TR1) zählt keine Referenzen. Der Kommentator hat auch eine andere Definition von "Smart Pointer" als ich gedacht habe. Er hat vielleicht Recht mit der Definition.

Weiteres Update: Der Kommentarthread unten leuchtet. All dies wird zum Lesen empfohlen.

3
thb

Es gibt auch andere Arten von intelligenten Zeigern. Möglicherweise möchten Sie einen speziellen intelligenten Zeiger für so etwas wie Netzwerkreplikation (der erkennt, ob auf ihn zugegriffen wird, und Änderungen an den Server sendet), den Änderungsverlauf aufzeichnet und die Tatsache kennzeichnet, dass auf ihn zugegriffen wurde, damit er untersucht werden kann, wann Sie speichern Daten auf der Festplatte und so weiter. Wir sind uns nicht sicher, ob dies im Zeiger die beste Lösung ist. Die Verwendung der integrierten Smart Pointer-Typen in Bibliotheken kann jedoch dazu führen, dass Personen an sie gebunden werden und die Flexibilität verlieren.

Die Anforderungen und Lösungen für die Speicherverwaltung können von Menschen unterschiedlichster Art sein, abgesehen von intelligenten Zeigern. Möglicherweise möchte ich den Speicher selbst verwalten. Möglicherweise reserviere ich Speicherplatz für Objekte in einem Speicherpool, damit er im Voraus reserviert wird und nicht zur Laufzeit (nützlich für Spiele). Ich verwende möglicherweise eine Implementierung von C++ mit Garbage Collected (C++ 11 macht dies möglich, obwohl es noch keine gibt). Oder vielleicht tue ich einfach nichts, was fortgeschritten genug ist, um mir Sorgen zu machen, ich kann wissen, dass ich nicht vergessen werde, Objekte nicht zu initialisieren und so weiter. Vielleicht bin ich nur zuversichtlich, dass ich das Gedächtnis ohne die Zeigerkrücke verwalten kann.

Die Integration mit C ist ebenfalls ein Problem.

Ein weiteres Problem ist, dass intelligente Zeiger Teil der STL sind. C++ ist so konzipiert, dass es ohne die STL verwendet werden kann.

2
David C. Bishop

Es hängt auch davon ab, in welchem ​​Bereich Sie arbeiten. Ich schreibe Spiele-Engines, wir vermeiden Boost wie die Pest, in Spielen ist der Overhead von Boost nicht akzeptabel. In unserer Core-Engine haben wir schließlich eine eigene Version von stl geschrieben (ähnlich wie ea stl).

Wenn ich eine Formularanwendung schreiben sollte, könnte ich die Verwendung von intelligenten Zeigern in Betracht ziehen; Aber sobald die Speicherverwaltung zur zweiten Natur geworden ist, wird es lästig, keine granulare Kontrolle über den Speicher zu haben.

0
Ugly Davis