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


156

Ich habe in einigen Artikeln gelesen, dass rohe Zeiger fast nie verwendet werden sollten. Stattdessen sollten sie immer in intelligente Zeiger eingeschlossen werden, unabhängig davon, ob es sich um Gültigkeitsbereichs- oder gemeinsam genutzte Zeiger handelt.

Ich bemerkte jedoch, 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 rohe Zeiger zurück oder erwarten sie. Gibt es dafür einen Grund? Sollte ich mich beim Schreiben einer öffentlichen API von intelligenten Zeigern fernhalten und warum?

Ich frage mich nur, warum intelligente Zeiger empfohlen werden, wenn viele große Projekte sie zu vermeiden scheinen.


22
Alle diese Bibliotheken, die Sie gerade genannt haben, wurden vor vielen Jahren gestartet. Intelligente Zeiger wurden erst in C ++ 11 zum Standard.
Chrisaycock

22
Intelligente Zeiger haben beispielsweise in eingebetteten / Echtzeitsystemen einen Overhead (Referenzzählung usw.), der kritisch sein kann. IMHO - Smart Pointer sind für faule Programmierer. Viele APIs verwenden auch den kleinsten gemeinsamen Nenner. Ich spüre, wie die Flammen um meine Füße lecken, während ich tippe!
Ed Heal

93
@EdHeal: Der Grund, warum Sie Flammen um Ihre Füße lecken können, ist, dass Sie in jeder Hinsicht völlig falsch liegen. In welchem ​​Overhead steckt zum Beispiel unique_ptr? Überhaupt keine. Sind Qt / WxWidgets auf eingebettete Systeme oder Echtzeitsysteme ausgerichtet? Nein, sie sind für Windows / Mac / Unix auf einem Desktop vorgesehen - höchstens. Intelligente Zeiger sind für Programmierer gedacht, die es richtig machen möchten.
Welpe

24
Auf Mobiltelefonen wird wirklich Java ausgeführt.
R. Martinho Fernandes

12
Intelligente Zeiger nur in C ++ 11 wirklich Standard? Was??? Diese Dinge werden seit mehr als 20 Jahren verwendet.
Kaz

Antworten:


124

Abgesehen von der Tatsache, dass viele Bibliotheken vor dem Aufkommen von Standard-Smart-Zeigern geschrieben wurden, ist der Hauptgrund wahrscheinlich das Fehlen einer Standard-C ++ - Anwendungsbinärschnittstelle (ABI).

Wenn Sie eine Nur-Header-Bibliothek schreiben, können Sie intelligente Zeiger und Standardcontainer nach Herzenslust weitergeben. Ihre Quelle steht Ihrer Bibliothek zur Kompilierungszeit zur Verfügung, sodass Sie sich nur auf die Stabilität ihrer Schnittstellen und nicht auf ihre Implementierungen verlassen können.

Aufgrund des Fehlens eines Standard-ABI können Sie diese Objekte jedoch im Allgemeinen nicht sicher über Modulgrenzen hinweg übergeben. Ein GCC unterscheidet shared_ptrsich wahrscheinlich von einem MSVC shared_ptr, der sich ebenfalls von einem Intel unterscheiden kann shared_ptr. Selbst mit demselben Compiler kann nicht garantiert werden, dass diese Klassen zwischen den Versionen binär kompatibel sind.

Unter dem Strich benötigen Sie einen Standard-ABI, auf den Sie sich verlassen können , wenn Sie eine vorgefertigte Version Ihrer Bibliothek verteilen möchten . 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 für C ++ nicht so gut. Einzelne Compiler können die Interaktion zwischen ihren eigenen Binärdateien übernehmen, sodass Sie die Möglichkeit haben, eine Version für jeden unterstützten Compiler zu verteilen, häufig für GCC und MSVC. Vor diesem Hintergrund exportieren die meisten Bibliotheken nur eine C-Schnittstelle - und das bedeutet Rohzeiger.

Nicht-Bibliothekscode sollte jedoch im Allgemeinen intelligente Zeiger gegenüber Raw bevorzugen.


17
Ich stimme Ihnen zu, selbst das Übergeben eines std :: string kann schmerzhaft sein. Dies sagt viel über C ++ als "großartige Sprache für Bibliotheken" aus.
Ha11owed

8
Das Fazit lautet eher: Wenn Sie eine Prebuild-Version verteilen möchten, müssen Sie dies für jeden Compiler tun, den Sie unterstützen möchten.
Josefx

6
@ Josefx: Ja, das ist traurig, aber wahr. Die einzige Alternative ist COM oder eine rohe C-Schnittstelle. Ich wünschte, die C ++ - Community würde sich um diese Art von Problemen sorgen. Ich meine, es ist nicht so, dass C ++ eine neue Sprache von vor 2 Jahren ist.
Robot Mess

3
Ich habe abgelehnt, weil das falsch ist. Die ABI-Probleme sind in den meisten Fällen mehr als beherrschbar. ABI ist zwar kaum benutzerfreundlich, aber auch kaum unüberwindbar.
Welpe

4
@ NathanAdams: Solche Software ist zweifellos beeindruckend und nützlich. Aber es behandelt das Symptom tieferer Probleme: Die C ++ - Semantik von Lebensdauer und Besitz liegt irgendwo zwischen verarmt und nicht existent. Diese Haufenfehler wären nicht aufgetreten, wenn die Sprache sie nicht zugelassen hätte. Intelligente Zeiger sind also kein Allheilmittel - sie sind ein Versuch, einige der Verluste auszugleichen, die durch die erstmalige Verwendung von C ++ entstehen.
Jon Purdy

40

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

  1. Intelligente Zeiger wurden erst kürzlich zum Standard. Bis dahin waren sie Teil anderer Bibliotheken
  2. Ihre Hauptanwendung 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 Rohzeigern zusätzliche Zeit- / Raumkosten verursacht; Die Benutzer der Bibliotheken möchten möglicherweise keinen solchen Overhead haben

Bearbeiten : Die Verwendung von intelligenten Zeigern liegt ganz beim Entwickler. Es hängt von verschiedenen Faktoren ab.

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

  2. Für das Projekt, das die Abwärtskompatibilität benötigt, möchten Sie möglicherweise nicht die intelligenten Zeiger verwenden, die über C ++ 11-spezifische Funktionen verfügen

Bearbeiten2 Es gibt eine Reihe von Abstimmungen innerhalb von 24 Stunden, da die Passage unten liegt. Ich verstehe nicht, warum die Antwort abgelehnt wird, obwohl unten nur ein Zusatzvorschlag und keine Antwort angegeben ist.
Mit C ++ können Sie jedoch immer die Optionen öffnen. :) 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 a austauschbar #defineist. Dies ist kein Zwang .

Muss beispielsweise T*explizit gelöscht werden, ein Smart Pointer jedoch nicht. Wir können eine Vorlage haben, Destroy()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 Rohzeiger direkt kopieren und für einen intelligenten Zeiger eine spezielle Operation verwenden.

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

Wo Assign()ist wie:

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

14
Am 3. Einige intelligente Zeiger haben zusätzliche Zeit- / Raumkosten, andere nicht, einschließlich der Kosten, std::auto_ptrdie seit langem Teil des Standards sind (und ich möchte std::auto_ptrals Rückgabetyp für Funktionen, die Objekte erstellen, auch wenn dies der Fall ist) fast überall sonst nutzlos). In C ++ 11 fallen std::unique_ptrkeine zusätzlichen Kosten gegenüber einem einfachen Zeiger an.
David Rodríguez - Dribeas

4
Genau ... es gibt eine schöne Symmetrie hinsichtlich des Auftretens unique_ptrund Verschwindens von auto_ptrCode-Targeting C ++ 03 sollte das spätere verwenden, während Code-Targeting C ++ 11 das erstere verwenden kann. Intelligente Zeiger gibt es nicht shared_ptr , es gibt viele Standards und keine Standards, einschließlich Vorschläge zum Standard, die alsmanaged_ptr
David Rodríguez - Dribeas

2
@iammilind, das sind interessante Punkte, aber das Lustige ist, dass wir, wenn wir intelligente Zeiger verwenden, wie anscheinend viele empfehlen würden, am Ende Code erstellen, der nicht mit den wichtigsten Bibliotheken kompatibel ist. Natürlich können wir die intelligenten Zeiger nach Bedarf ein- und auspacken, aber es scheint viel Aufwand zu bedeuten und würde inkonsistenten Code erzeugen (manchmal beschäftigen wir uns mit intelligenten Zeigern, manchmal nicht).
Laurent

7
Die Aussage, dass intelligente Zeiger "zusätzliche Zeit- / Raumkosten" verursachen, ist etwas irreführend. Alle intelligenten Zeiger mit Ausnahme der unique_ptrLaufzeitkosten sind jedoch unique_ptrbei weitem die am häufigsten verwendeten. Das Codebeispiel Sie bieten auch irreführend, weil unique_ptrund T*völlig unterschiedliche Konzepte sind. Die Tatsache, dass Sie beide als bezeichnen, typeerweckt den Eindruck, dass sie gegeneinander ausgetauscht werden können.
Leerzeiger

12
Sie können nicht so tippen, diese Typen sind in keiner Weise gleichwertig. Das Schreiben solcher Typedefs ist problematisch.
Alex B

35

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

  • Nicht-Standards, daher neigt jede Bibliothek dazu, ihre eigenen neu zu erfinden (Probleme mit dem NIH-Syndrom und den Abhängigkeiten).
  • potenzielle Kosten

Der standardmäßige Smart Pointer ist insofern kostenlos unique_ptr. Leider erfordert es eine C ++ 11-Verschiebungssemantik, die erst kürzlich veröffentlicht wurde. Alle anderen intelligenten Zeiger haben Kosten ( shared_ptr, intrusive_ptr) oder eine weniger als ideale Semantik ( auto_ptr).

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

Nur wenige große Compiler implementieren den größten Teil von C ++ 11 und nur in ihren neuesten 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 breite Akzeptanz der neuen und glänzenden intelligenten Zeiger etwas ausschließt.


12

Sie sollten sich nicht von intelligenten Zeigern fernhalten, da diese insbesondere in Anwendungen verwendet werden, in denen Sie ein Objekt weitergeben 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 nur in einigen Klassen Zeiger und intelligente Zeiger verwendeten (3-5% aller Klassen).

Das Übergeben 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 von uns verwendete Bibliothek uns dazu zwang.

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


Ich stimme Ihnen nicht genau zu, aber ich möchte darauf hinweisen, dass es eine Denkschule gibt, die die Weitergabe variabler Referenzen in den meisten Fällen ablehnt. Ich gebe zu, dass ich an dieser Schule festhalte. Ich bevorzuge Funktionen, um ihre Argumente nicht zu ändern. Soweit ich weiß, tragen die variablen Referenzen von C ++ jedenfalls nicht dazu bei, eine falsche Handhabung der Objekte zu verhindern, auf die sie verweisen, was intelligente Zeiger beabsichtigen.
thb

2
Sie haben const dafür (anscheinend kann ich kommentieren: D).
Robot Mess

9

Qt hat viele Teile der Standardbibliothek sinnlos neu erfunden, um Java zu werden. Ich glaube, dass es jetzt tatsächlich seine eigenen intelligenten Zeiger hat, aber im Allgemeinen ist es kaum ein Höhepunkt des Designs. Soweit mir bekannt ist, wurde wxWidgets lange vor dem Schreiben verwendbarer intelligenter Zeiger entwickelt.

Was Boost betrifft, 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 das Eigentum durchzusetzen. Wenn die API keine Besitzersemantik hat, warum dann einen intelligenten Zeiger verwenden?


19
Qt wurde geschrieben, bevor ein Großteil der Funktionalität auf den Plattformen, die es verwenden wollte, ausreichend verbreitet war. Es hat seit langer Zeit intelligente Zeiger und verwendet sie, um Ressourcen in fast allen Q * -Klassen implizit gemeinsam zu nutzen.
Rubenvb

6
Jede GUI-Bibliothek erfindet das Rad unnötig neu. Sogar Strings, Qt hat QString, wxWidgets hat wxString, MFC hat den schrecklichen Namen CString. Ist ein UTF-8 nicht std::stringgut genug für 99% der GUI-Aufgaben?
Inverse

10
@Inverse QString wurde erstellt, als std :: string nicht vorhanden war.
MrFox

Überprüfen Sie, wann qt erstellt wurde und welche intelligenten Zeiger zu diesem Zeitpunkt verfügbar waren.
Dainius

3

Gute Frage. Ich kenne die spezifischen Artikel, auf die Sie sich beziehen, nicht, aber ich habe von Zeit zu Zeit ähnliche Dinge gelesen. Mein Verdacht ist, dass die Autoren solcher Artikel dazu neigen, eine Tendenz gegen die Programmierung im C ++ - Stil zu hegen. Wenn der Writer nur dann in C ++ programmiert, wenn er muss, und dann so bald wie möglich zu Java oder Ähnlichem zurückkehrt, teilt er die C ++ - Denkweise nicht wirklich.

Man vermutet, dass einige oder die meisten der gleichen Autoren Speichermanager zum Sammeln von Müll bevorzugen. Ich nicht, aber ich denke anders als sie.

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

Eines der hervorragenden Dinge an C ++ ist die Unterstützung der Programmierung eingebetteter Systeme. Die Verwendung von bloßen Zeigern ist ein Teil davon.

Update: Ein Kommentator hat korrekt festgestellt, dass C ++ 's new unique_ptr(verfügbar seit TR1) keine Referenzen zählt. Der Kommentator hat auch eine andere Definition von "Smart Pointer" als ich denke. Er kann mit der Definition Recht haben.

Weiteres Update: Der Kommentarthread unten leuchtet auf. Es wird empfohlen, alles zu lesen.


2
Zunächst einmal ist die Programmierung eingebetteter Systeme eine große Minderheit aller Programmierungen und ziemlich irrelevant. C ++ ist eine Allzwecksprache. Zweitens shared_ptrhält eine Referenzzählung. Es gibt viele andere Smart-Pointer-Typen, die überhaupt keinen Referenzzähler haben. Schließlich sind die genannten Bibliotheken auf Plattformen ausgerichtet, die über genügend Ressourcen verfügen. Nicht dass ich der Downvoter gewesen wäre, aber alles was ich sage ist, dass dein Beitrag voller Fehler ist.
Welpe

2
@thb - Ich stimme dir zu. DeadMG - Bitte versuchen Sie, ohne eingebettete Systeme zu leben. Ja, einige intelligente Zeiger haben keinen Overhead, andere jedoch. Das OP erwähnt Bibliotheken. Boost enthält beispielsweise Teile, die von eingebetteten Systemen verwendet werden. Intelligente Zeiger können jedoch für bestimmte Anwendungen ungeeignet sein.
Ed Heal

2
@EdHeal: Nicht ohne eingebettete Systeme leben! = Programmieren für sie ist keine winzige, irrelevante Minderheit. Intelligente Zeiger eignen sich für jede Situation, in der Sie die Lebensdauer einer Ressource verwalten müssen.
Welpe

4
shared_ptrhat keinen Overhead. Es hat nur dann Overhead, wenn Sie keine thread-sichere Semantik für gemeinsam genutzte Eigentumsrechte benötigen.
R. Martinho Fernandes

1
Nein, shared_ptr hat einen erheblichen Overhead über dem Minimum, das für eine threadsichere Semantik für gemeinsam genutzte Eigentümer erforderlich ist. Insbesondere wird ein Heap-Block getrennt von dem tatsächlichen Objekt zugewiesen, das Sie freigeben, und zwar ausschließlich zum Speichern des Refcounts. intrusive_ptr ist effizienter, aber (wie shared_ptr) wird auch davon ausgegangen, dass jeder Zeiger auf das Objekt ein intrusive_ptr ist. Sie können mit einem benutzerdefinierten Ref-Counting-Zeiger, wie ich es in meiner App tue, einen noch geringeren Overhead als intrusive_ptr erzielen und dann T * verwenden, wenn Sie garantieren können, dass mindestens ein intelligenter Zeiger den T * -Wert überlebt.
Qwertie

2

Es gibt auch andere Arten von intelligenten Zeigern. Möglicherweise möchten Sie einen speziellen intelligenten Zeiger für eine Netzwerkreplikation (einer, der erkennt, ob auf ihn zugegriffen wird, und Änderungen an den Server oder ähnliches sendet), einen Änderungsverlauf aufzeichnet und die Tatsache markiert, dass auf ihn zugegriffen wurde, damit untersucht werden kann, wann Sie speichern Daten auf der Festplatte und so weiter. Ich bin mir nicht sicher, ob dies die beste Lösung für den Zeiger ist. Die Verwendung der integrierten intelligenten Zeigertypen in Bibliotheken kann jedoch dazu führen, dass Personen an sie gebunden werden und die Flexibilität verlieren.

Menschen können alle möglichen unterschiedlichen Anforderungen und Lösungen für die Speicherverwaltung haben, die über intelligente Zeiger hinausgehen. Ich möchte den Speicher möglicherweise selbst verwalten. Ich kann Speicherplatz für Dinge in einem Speicherpool zuweisen, damit dieser im Voraus und nicht zur Laufzeit zugewiesen wird (nützlich für Spiele). Ich verwende möglicherweise eine durch Müll gesammelte Implementierung von C ++ (C ++ 11 macht dies möglich, obwohl noch keine vorhanden ist). Oder vielleicht mache ich einfach nichts Fortgeschrittenes, um mir Sorgen um sie zu machen. Ich kann wissen, dass ich nicht vergessen werde, nicht initialisierte Objekte zu verwenden 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.


" Ein weiteres Problem ist, dass intelligente Zeiger Teil der STL sind. "
Neugieriger

0

Es hängt auch davon ab, in welcher Domäne Sie arbeiten. Ich schreibe Game-Engines für den Lebensunterhalt. Wir vermeiden Boost wie die Pest. In Spielen ist der Overhead von Boost nicht akzeptabel. In unserer Core Engine haben wir schließlich unsere eigene Version von stl geschrieben (ähnlich wie die ea stl).

Wenn ich eine Formularanwendung schreiben würde, könnte ich die Verwendung intelligenter Zeiger in Betracht ziehen; Aber sobald die Speicherverwaltung zur zweiten Natur geworden ist, wird es ziemlich ärgerlich, keine granulare Kontrolle über den Speicher zu haben.


3
Es gibt keinen "Overhead of Boost".
Neugieriger

4
Share_ptr hat meine Spiel-Engine noch nie in nennenswertem Maße verlangsamt. Sie haben jedoch den Produktions- und Debugging-Prozess beschleunigt. Und was genau meinst du mit "dem Overhead des Boosts"? Das ist eine ziemlich große Decke zum Gießen.
derpface

@curiousguy: Es ist der Kompilierungsaufwand all dieser Header und Makro + Vorlage Voodoo ...
Einpoklum
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.