Ich habe Probleme, die Verwendung intelligenter Zeiger als Klassenmitglieder in C ++ 11 zu verstehen. Ich habe viel über intelligente Zeiger gelesen und glaube zu verstehen, wie unique_ptr
und shared_ptr
/ oder wie ich weak_ptr
im Allgemeinen arbeite. Was ich nicht verstehe, ist die tatsächliche Verwendung. Es scheint, als würde jeder empfehlen unique_ptr
, fast immer den Weg zu gehen. Aber wie würde ich so etwas umsetzen:
class Device {
};
class Settings {
Device *device;
public:
Settings(Device *device) {
this->device = device;
}
Device *getDevice() {
return device;
}
};
int main() {
Device *device = new Device();
Settings settings(device);
// ...
Device *myDevice = settings.getDevice();
// do something with myDevice...
}
Angenommen, ich möchte die Zeiger durch intelligente Zeiger ersetzen. A unique_ptr
würde wegen nicht funktionieren getDevice()
, oder? Das ist also die Zeit, in der ich shared_ptr
und benutze weak_ptr
? Keine Möglichkeit zu benutzen unique_ptr
? Scheint mir, dass es in den meisten Fällen shared_ptr
sinnvoller ist, wenn ich keinen Zeiger in einem wirklich kleinen Bereich verwende?
class Device {
};
class Settings {
std::shared_ptr<Device> device;
public:
Settings(std::shared_ptr<Device> device) {
this->device = device;
}
std::weak_ptr<Device> getDevice() {
return device;
}
};
int main() {
std::shared_ptr<Device> device(new Device());
Settings settings(device);
// ...
std::weak_ptr<Device> myDevice = settings.getDevice();
// do something with myDevice...
}
Ist das der richtige Weg? Vielen Dank!
quelle
device
an den Konstruktor von übergebensettings
haben, möchten Sie dennoch im aufrufenden Bereich oder nur über darauf verweisen könnensettings
? Wenn letzteresunique_ptr
nützlich ist. Haben Sie auch ein Szenario, in dem der Rückgabewert vongetDevice()
istnull
. Wenn nicht, geben Sie einfach eine Referenz zurück.shared_ptr
ist in 8/10 Fällen korrekt. Die anderen 2/10 werden zwischenunique_ptr
und aufgeteiltweak_ptr
. Wird auchweak_ptr
allgemein verwendet, um Zirkelverweise zu brechen; Ich bin nicht sicher, ob Ihre Verwendung als korrekt angesehen wird.device
Datenmitglied? Das muss man zuerst entscheiden.unique_ptr
stattdessen einen verwenden und den Besitz aufgeben könnte , wenn ich den Konstruktor aufrufe, wenn ich weiß , dass ich ihn vorerst nicht mehr brauche. Aber als Designer derSettings
Klasse weiß ich nicht, ob der Anrufer auch eine Referenz behalten möchte. Möglicherweise wird das Gerät an vielen Orten verwendet. Ok, vielleicht ist das genau dein Punkt. In diesem Fall wäre ich nicht der alleinige Eigentümer, und dann würde ich wohl shared_ptr verwenden. Und: Smart Points ersetzen also Zeiger, aber keine Referenzen, oder?Antworten:
Nein, nicht unbedingt. Hierbei ist es wichtig, die geeignete Eigentumsrichtlinie für Ihr
Device
Objekt zu bestimmen , dh wer der Eigentümer des Objekts sein wird, auf das Ihr (intelligenter) Zeiger zeigt.Wird es nur die Instanz des
Settings
Objekts sein ? Wird dasDevice
Objekt zerstört werden müssen , automatisch , wenn dasSettings
Objekt zerstört wird, oder sollte es das Objekt überleben?Im ersten Fall
std::unique_ptr
benötigen Sie Folgendes, da esSettings
den einzigen (eindeutigen) Eigentümer des spitzen Objekts und das einzige Objekt darstellt, das für dessen Zerstörung verantwortlich ist.Unter dieser Annahme
getDevice()
sollte ein einfacher Beobachtungszeiger zurückgegeben werden (Beobachtungszeiger sind Zeiger, die das spitze Objekt nicht am Leben erhalten). Die einfachste Art, einen Zeiger zu beobachten, ist ein Rohzeiger:[ ANMERKUNG 1: Sie fragen sich vielleicht, warum ich hier Rohzeiger verwende, wenn alle immer wieder sagen, dass Rohzeiger schlecht, unsicher und gefährlich sind. Eigentlich ist das eine wertvolle Warnung, aber es ist wichtig, sie in den richtigen Kontext zu stellen: Rohzeiger sind schlecht, wenn sie für die manuelle Speicherverwaltung verwendet werden , dh für das Zuweisen und Freigeben von Objekten durch
new
unddelete
. Wenn es nur als Mittel verwendet wird, um eine Referenzsemantik zu erreichen und nicht besitzende, beobachtende Zeiger weiterzugeben, gibt es in rohen Zeigern nichts an sich Gefährliches, außer vielleicht der Tatsache, dass man darauf achten sollte, einen baumelnden Zeiger nicht zu dereferenzieren. - ENDE ANMERKUNG 1 ][ ANMERKUNG 2: Wie in den Kommentaren festgestellt wurde , könnte (und sollte) in diesem speziellen Fall, in dem der Besitz eindeutig ist und das besessene Objekt immer vorhanden sein wird (dh das interne Datenelement
device
wird es niemals seinnullptr
), funktionierengetDevice()
(und sollte es vielleicht). Geben Sie eine Referenz anstelle eines Zeigers zurück. Obwohl dies zutrifft, habe ich beschlossen, hier einen Rohzeiger zurückzugeben, da dies eine kurze Antwort war, die man auf den Fall verallgemeinern könnte, in dem diesdevice
möglich istnullptr
, und um zu zeigen, dass Rohzeiger in Ordnung sind, solange man sie nicht verwendet manuelle Speicherverwaltung. - ENDE ANMERKUNG 2 ]Die Situation ist natürlich radikal anders, wenn Ihr
Settings
Objekt nicht das ausschließliche Eigentum an dem Gerät haben sollte. Dies könnte beispielsweise der Fall sein, wenn die Zerstörung desSettings
Objekts nicht auch die Zerstörung des spitzenDevice
Objekts implizieren sollte .Dies können nur Sie als Designer Ihres Programms sagen. Anhand des von Ihnen angegebenen Beispiels fällt es mir schwer zu sagen, ob dies der Fall ist oder nicht.
Um dies herauszufinden, können Sie sich fragen, ob es andere Objekte
Settings
gibt, die berechtigt sind, dasDevice
Objekt am Leben zu erhalten, solange sie einen Zeiger darauf halten, anstatt nur passive Beobachter zu sein. Wenn dies tatsächlich der Fall ist, benötigen Sie eine Richtlinie für gemeinsames Eigentum , die Folgendesstd::shared_ptr
bietet:Beachten Sie, dass dies
weak_ptr
ein Beobachtungszeiger ist , kein besitzender Zeiger - mit anderen Worten, er hält das spitze Objekt nicht am Leben, wenn alle anderen besitzenden Zeiger auf das spitze Objekt den Gültigkeitsbereich verlassen.Der Vorteil
weak_ptr
gegenüber einem normalen Rohzeiger ist , dass man sicher sagen kann , obweak_ptr
sie baumelt oder nicht (dh ob er auf ein gültiges Objekt zeigt, oder wenn das Objekt ursprünglich zeigt auf zerstört worden ist ). Dies kann durch Aufrufen derexpired()
Member-Funktion für dasweak_ptr
Objekt erfolgen.quelle
weak_ptr
ist immer eine Alternative zu rohen Beobachtungszeigern. In gewisser Hinsicht ist es sicherer, da Sie überprüfen können, ob es baumelt, bevor Sie es dereferenzieren, aber es ist auch mit einem gewissen Overhead verbunden. Wenn Sie leicht garantieren können, dass Sie einen baumelnden Zeiger nicht dereferenzieren, sollten Sie in der Lage sein, rohe Zeiger zu beobachtengetDevice()
eine Referenz zurückgeben zu lassen , nicht wahr? Der Anrufer müsste also nicht nachsehennullptr
.auto myDevice = settings.getDevice()
erstellt eine neue Instanz desDevice
aufgerufenen TypsmyDevice
und kopiert sie aus der Instanz, auf die durch die zurückgegebene Referenz verwiesen wirdgetDevice()
. Wenn SiemyDevice
eine Referenz sein möchten , müssen Sie dies tunauto& myDevice = settings.getDevice()
. Wenn ich also nichts vermisse, sind wir wieder in der gleichen Situation, die wir ohne Verwendung hattenauto
.unique_ptr
eröffnet die Übergabe eines modifizierbaren Objekts an einen Client die Möglichkeit, dass der Client von diesem Objekt abweicht , wodurch das Eigentum erworben wird und Sie einen Nullzeiger (eindeutigen Zeiger) erhalten.const_cast
s interessiert ), würde ich es persönlich nicht tun. Es enthüllt ein Implementierungsdetail, dh die Tatsache, dass das Eigentum einzigartig ist und durch a realisiert wirdunique_ptr
. Ich sehe die Dinge so: Wenn Sie das Eigentum übergeben möchten, müssen Sie einen intelligenten Zeiger übergebenunique_ptr
oder zurückgeben ( odershared_ptr
, abhängig von der Art des Eigentums). Wenn Sie den Besitz nicht übergeben / zurückgeben möchten / müssen, verwenden Sie einen (ordnungsgemäßconst
qualifizierten) Zeiger oder eine Referenz, hauptsächlich abhängig davon, ob das Argument null sein kann oder nicht.week_ptr
wird nur für Referenzschleifen verwendet. Der Abhängigkeitsgraph muss ein azyklisch gerichteter Graph sein. In gemeinsam genutzten Zeigern gibt es 2 Referenzzählungen: 1 fürshared_ptr
s und 1 für alle Zeiger (shared_ptr
undweak_ptr
). Wenn alleshared_ptr
s entfernt sind, wird der Zeiger gelöscht. Wenn ein Zeiger von benötigt wirdweak_ptr
,lock
sollte verwendet werden, um den Zeiger zu erhalten, falls vorhanden.quelle
shared_ptr
? Können Sie bitte erklären, warum? Soweit ichweak_ptr
weiß , muss nicht gezählt werden, dashared_ptr
beim Bearbeiten des Objekts einfach ein neues erstellt wird (sofern das zugrunde liegende Objekt noch vorhanden ist).lock
. Die Boost- Version ist auch beim Referenzzählen threadsicher (delete
wird nur einmal aufgerufen).weak_ptr::lock()
ob das Objekt abgelaufen ist, muss es den "Steuerblock" überprüfen, der den ersten Referenzzähler und Zeiger auf das Objekt enthält, damit der Steuerblock nicht zerstört wird, solange nochweak_ptr
Objekte verwendet werden.weak_ptr
Daher muss die Anzahl der Objekte verfolgt werden, was der zweite Referenzzähler tut. Das Objekt wird zerstört, wenn die erste Ref-Zählung auf Null fällt. Der Steuerblock wird zerstört, wenn die zweite Ref-Zählung auf Null fällt.