class B;
class A
{
public:
A ()
: m_b(new B())
{
}
shared_ptr<B> GimmeB ()
{
return m_b;
}
private:
shared_ptr<B> m_b;
};
Nehmen wir an, B ist eine Klasse, die semantisch nicht außerhalb der Lebensdauer von A existieren sollte, dh es macht absolut keinen Sinn, dass B für sich allein existiert. Sollte GimmeB
ein shared_ptr<B>
oder ein zurückgeben B*
?
Ist es im Allgemeinen empfehlenswert, die Verwendung von Rohzeigern in C ++ - Code anstelle von intelligenten Zeigern vollständig zu vermeiden?
Ich bin der Meinung, dass dies shared_ptr
nur verwendet werden sollte, wenn eine explizite Übertragung oder Aufteilung des Eigentums erfolgt, was meiner Meinung nach außerhalb von Fällen, in denen eine Funktion Speicher reserviert, mit einigen Daten füllt und zurückgibt und Verständnis besteht, ziemlich selten ist zwischen dem Anrufer und dem Angerufenen, dass der erstere jetzt für diese Daten "verantwortlich" ist.
quelle
Antworten:
Ihre Analyse ist ganz richtig, denke ich. In dieser Situation würde ich auch ein nacktes zurückgeben
B*
, oder sogar ein,[const] B&
wenn das Objekt garantiert niemals null ist.Nachdem ich einige Zeit hatte, um intelligente Zeiger zu lesen, kam ich zu einigen Richtlinien, die mir in vielen Fällen sagen, was ich tun soll:
std::unique_ptr
. Der Anrufer kann es einem zuweisen,std::shared_ptr
wenn er möchte.std::shared_ptr
ist eigentlich ziemlich selten, und wenn es sinnvoll ist, ist es im Allgemeinen offensichtlich: Sie geben dem Aufrufer an, dass dies die Lebensdauer des Objekts, auf das verwiesen wird, über die Lebensdauer des Objekts hinaus verlängert, das ursprünglich die Ressource verwaltet hat. Die Rückgabe gemeinsamer Zeiger aus Fabriken ist keine Ausnahme: Sie müssen dies tun, z. wenn Sie verwendenstd::enable_shared_from_this
.std::weak_ptr
, außer wenn Sie dielock
Methode verstehen wollen . Dies hat einige Verwendungszwecke, aber sie sind selten. Wenn in Ihrem Beispiel die Lebensdauer desA
Objekts aus Sicht des Aufrufers nicht deterministisch gewesen wäre, wäre dies zu berücksichtigen gewesen.nullptr
Wert nicht verwenden.quelle
std::weak_ptr
" Hören! hören!std::unique_ptr
(oder vielleicht habe ich nicht ganz verstanden, was Sie meinten).Die Frage "Wann soll ich verwenden
shared_ptr
und wann soll ich Rohzeiger verwenden?" hat eine sehr einfache Antwort:unique_ptr
oder,scope_ptr
wenn Sie das Objekt eindeutig besitzen möchten. Dies ist die nützlichste Option und sollte in den meisten Fällen verwendet werden. Eindeutiges Eigentum kann auch ausgedrückt werden, indem einfach ein Objekt direkt erstellt wird, anstatt einen Zeiger zu verwenden (dies ist sogar besser als die Verwendung von aunique_ptr
, wenn dies möglich ist).shared_ptr
oder,intrusive_ptr
wenn Sie den Zeiger gemeinsam besitzen möchten. Dies kann verwirrend und ineffizient sein und ist oft keine gute Option. Shared Ownership kann in einigen komplexen Designs nützlich sein, sollte jedoch generell vermieden werden, da dies zu schwer verständlichem Code führt.shared_ptr
s führen eine völlig andere Aufgabe aus als Rohzeiger, und wedershared_ptr
s noch Rohzeiger sind die beste Option für den Großteil des Codes.quelle
Folgendes ist eine gute Faustregel:
std::unique_ptr<>
ist dies eine gute Wahl. Oft der Fall bei Werksfunktionen.std::shared_ptr<>
oderboost::intrusive_ptr<>
.Es ist am besten, gemeinsames Eigentum zu vermeiden, zum Teil, weil sie im Hinblick auf das Kopieren am teuersten sind und
std::shared_ptr<>
doppelt so viel Speicherplatz wie ein einfacher Zeiger benötigen, aber vor allem, weil sie schlechten Designs förderlich sind, bei denen es keine eindeutigen Eigentümer gibt. Dies führt wiederum zu einem Haarball von Objekten, die nicht zerstört werden können, weil sie gemeinsame Zeiger aufeinander halten.Das beste Design ist dort, wo ein klares Eigentum festgelegt und hierarchisch ist, so dass im Idealfall überhaupt keine intelligenten Zeiger erforderlich sind. Wenn es beispielsweise eine Factory gibt, die eindeutige Objekte erstellt oder vorhandene zurückgibt, ist es sinnvoll, dass die Factory die von ihr erstellten Objekte besitzt und sie nur nach Wert in einem assoziativen Container (z. B.
std::unordered_map
) aufbewahrt, damit sie einfach zurückgegeben werden kann Zeiger oder Verweise auf seine Benutzer. Diese Factory muss eine Lebensdauer haben, die vor ihrem ersten Benutzer beginnt und nach ihrem letzten Benutzer endet (die hierarchische Eigenschaft), damit Benutzer keinen Zeiger auf ein bereits zerstörtes Objekt haben können.quelle
Wenn Sie nicht möchten, dass der Angerufene von GimmeB () die Lebensdauer des Zeigers verlängern kann, indem Sie eine Kopie des ptr behalten, nachdem die Instanz von A gestorben ist, sollten Sie auf keinen Fall einen shared_ptr zurückgeben.
Wenn der Angerufene den zurückgegebenen Zeiger nicht für längere Zeit behalten soll, dh es besteht kein Risiko, dass die Lebensdauer von A vor der des Zeigers abläuft, ist ein roher Zeiger besser. Eine noch bessere Wahl ist jedoch die Verwendung einer Referenz, es sei denn, es gibt einen guten Grund, einen tatsächlichen Rohzeiger zu verwenden.
Und schließlich, falls der zurückgegebene Zeiger nach Ablauf der Lebensdauer der A-Instanz vorhanden sein kann, Sie aber nicht möchten, dass der Zeiger selbst die Lebensdauer des B verlängert, können Sie ein schwaches_ptr zurückgeben, das Sie zum Testen verwenden können ob es noch existiert.
Das Fazit ist, dass es normalerweise eine schönere Lösung gibt als die Verwendung eines Rohzeigers.
quelle
Ich stimme Ihrer Meinung zu
shared_ptr
am besten verwendet wird, wenn Ressourcen explizit gemeinsam genutzt werden. Es stehen jedoch auch andere Arten von intelligenten Zeigern zur Verfügung.In Ihrem genauen Fall: Warum nicht eine Referenz zurückgeben?
Ein Zeiger deutet darauf hin, dass die Daten möglicherweise null sind. Hier befindet sich jedoch immer eine
B
in IhrerA
, sodass sie niemals null sein wird. Die Referenz bestätigt dieses Verhalten.Davon abgesehen habe ich Leute gesehen, die die Verwendung von Anwendungen
shared_ptr
auch in nicht gemeinsam genutzten Umgebungen befürworteten undweak_ptr
Handles gaben , mit der Idee, die Anwendung zu "sichern" und veraltete Zeiger zu vermeiden. Da Sie eineshared_ptr
von der wiederherstellen könnenweak_ptr
(und dies ist die einzige Möglichkeit, die Daten tatsächlich zu manipulieren), ist dies leider immer noch ein gemeinsames Eigentum, auch wenn dies nicht beabsichtigt war.Hinweis: Es gibt einen subtilen Fehler
shared_ptr
, bei dem eine Kopie von standardmäßigA
dieselbeB
wie das Original hat, es sei denn, Sie schreiben explizit einen Kopierkonstruktor und einen Kopierzuweisungsoperator. Und natürlich würden Sie keinen rohen Zeiger verwendenA
, um a zu haltenB
, oder :)?Eine andere Frage ist natürlich, ob Sie dies tatsächlich tun müssen. Einer der Grundsätze für gutes Design ist die Kapselung . Um eine Kapselung zu erreichen:
Vielleicht ist die eigentliche Antwort auf Ihre Frage, dass anstatt eine Referenz oder einen Zeiger darauf zu verraten
B
, diese nur überA
die Benutzeroberfläche geändert werden sollte.quelle
shared_ptr
Lösung verwendet wird. Bei der Rückgabe einer Referenz kann es sich jedoch durchaus um ein reguläres Attribut handeln, und reguläre Attribute werden nicht flach kopiert.gimmeB
(obwohl die Antwort die innere Darstellung beeinflussen könnte). Ich werde versuchen, die Notiz zu klären.Im Allgemeinen würde ich die Verwendung von rohen Zeigern so weit wie möglich vermeiden, da sie eine sehr zweideutige Bedeutung haben - Sie müssen möglicherweise die Zuordnung des Pointees aufheben, aber möglicherweise nicht, und nur von Menschen gelesene und geschriebene Dokumentationen sagen Ihnen, was der Fall ist. Und die Dokumentation ist immer schlecht, veraltet oder missverstanden.
Wenn der Besitz ein Problem darstellt, verwenden Sie einen intelligenten Zeiger. Wenn nicht, würde ich eine Referenz verwenden, wenn dies praktikabel ist.
quelle
Beide weisen darauf hin, dass B ein Mitglied von A ist und nur einen Referenz-Accessor zurückgibt. Überentwickeln Sie das?
quelle
Ich habe festgestellt, dass die C ++ - Kernrichtlinien einige sehr nützliche Hinweise für diese Frage enthalten:
Die Verwendung eines Rohzeigers (T *) oder eines intelligenteren Zeigers hängt davon ab, wem das Objekt gehört (dessen Verantwortung es ist, den Speicher des Objekts freizugeben).
Eigentümer <>, Bereich <> ist in der Microsoft GSL-Bibliothek definiert
Hier sind die Faustregeln:
1) Verwenden Sie niemals einen Rohzeiger (oder keine eigenen Typen), um den Besitz zu übergeben
2) Smart Pointer sollte nur verwendet werden, wenn eine Besitzersemantik beabsichtigt ist
3) T * oder Eigentümer bezeichnen ein einzelnes Objekt (nur)
4) Verwenden Sie Vektor / Array / Spanne für Array
5) Meines Wissens nach wird shared_ptr normalerweise verwendet, wenn Sie nicht wissen, wer das Objekt freigibt. Beispielsweise wird ein Objekt von einem Multithread verwendet
quelle
Es wird empfohlen, keine rohen Zeiger zu verwenden, aber Sie können nicht einfach alles durch ersetzen
shared_ptr
. In diesem Beispiel gehen Benutzer Ihrer Klasse davon aus, dass es in Ordnung ist, die Lebensdauer von B über die von A hinaus zu verlängern, und entscheiden sich möglicherweise aus eigenen Gründen, das zurückgegebene B-Objekt für einige Zeit zu halten. Sie sollten a zurückgebenweak_ptr
oder, wenn B bei Zerstörung von A absolut nicht existieren kann, einen Verweis auf B oder einfach einen Rohzeiger.quelle
weak_ptr
löst das Problem nicht wirklich. "Benutzer Ihrer Klasse gehen davon aus, dass es in Ordnung ist, die Lebensdauer von B zu verlängern", da sie den Befehl "soft_ptr" an dem Punkt sperren können, an dem A zerstört wird, und somit die Lebensdauer von B verlängern können. Es könnte sie jedoch dazu bringen, darüber nachzudenken, und natürlich kann B auch gehen, wenn sie es nicht verschlossen haben, wenn A zerstört wird.Wenn Sie sagen: "Nehmen wir an, B ist eine Klasse, die semantisch nicht außerhalb der Lebensdauer von A existieren sollte."
Dies sagt mir, dass B logisch sein sollte nicht ohne A existieren , aber was ist mit physisch existierend? Wenn Sie sicher sein können, dass niemand versucht, ein * B nach A dtors zu verwenden, ist ein roher Zeiger möglicherweise in Ordnung. Andernfalls kann ein intelligenterer Zeiger angebracht sein.
Wenn Clients einen direkten Zeiger auf A haben, müssen Sie darauf vertrauen, dass sie damit angemessen umgehen. Versuchen Sie nicht, es zu speichern usw.
quelle