Der Zweck einer bestimmten shared_ptr
Instanz besteht darin, (so weit wie möglich) zu gewährleisten shared_ptr
, dass das Objekt, auf das sie zeigt, solange es im Geltungsbereich liegt, weiterhin vorhanden ist, da seine Referenzanzahl mindestens 1 beträgt.
Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
// sp points to an object that cannot be destroyed during this function
}
shared_ptr
Wenn Sie also einen Verweis auf a verwenden , deaktivieren Sie diese Garantie. Also in Ihrem zweiten Fall:
Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here
{
...
sp->do_something();
...
}
Woher wissen Sie, dass dies sp->do_something()
aufgrund eines Nullzeigers nicht explodiert?
Es hängt alles davon ab, was sich in diesen '...' Abschnitten des Codes befindet. Was ist, wenn Sie während des ersten "..." etwas aufrufen, das den Nebeneffekt (irgendwo in einem anderen Teil des Codes) hat, ein shared_ptr
Objekt für dasselbe Objekt zu löschen? Und was ist, wenn es das einzige ist shared_ptr
, das sich von diesem Objekt unterscheidet? Bye bye Objekt, genau dort, wo Sie versuchen möchten, es zu verwenden.
Es gibt also zwei Möglichkeiten, diese Frage zu beantworten:
Untersuchen Sie die Quelle Ihres gesamten Programms sehr sorgfältig, bis Sie sicher sind, dass das Objekt während des Funktionskörpers nicht stirbt.
Ändern Sie den Parameter wieder in ein eindeutiges Objekt anstelle einer Referenz.
Allgemeiner Ratschlag, der hier gilt: Nehmen Sie aus Gründen der Leistung keine riskanten Änderungen an Ihrem Code vor, bis Sie Ihr Produkt in einer realistischen Situation in einem Profiler zeitlich festgelegt und abschließend gemessen haben, dass die von Ihnen gewünschte Änderung a signifikanter Unterschied zur Leistung.
Update für Kommentator JQ
Hier ist ein erfundenes Beispiel. Es ist absichtlich einfach, daher ist der Fehler offensichtlich. In realen Beispielen ist der Fehler nicht so offensichtlich, weil er in Schichten von echten Details verborgen ist.
Wir haben eine Funktion, die irgendwo eine Nachricht sendet. Es kann sich um eine große Nachricht handeln. Anstatt eine zu verwenden std::string
, die wahrscheinlich kopiert wird, wenn sie an mehrere Stellen weitergegeben wird, verwenden wir eine shared_ptr
an eine Zeichenfolge:
void send_message(std::shared_ptr<std::string> msg)
{
std::cout << (*msg.get()) << std::endl;
}
(Wir "senden" es für dieses Beispiel einfach an die Konsole).
Jetzt möchten wir eine Funktion hinzufügen, um die vorherige Nachricht zu speichern. Wir möchten das folgende Verhalten: Es muss eine Variable vorhanden sein, die die zuletzt gesendete Nachricht enthält. Während jedoch eine Nachricht gesendet wird, darf keine vorherige Nachricht vorhanden sein (die Variable sollte vor dem Senden zurückgesetzt werden). Also deklarieren wir die neue Variable:
std::shared_ptr<std::string> previous_message;
Dann ändern wir unsere Funktion gemäß den von uns festgelegten Regeln:
void send_message(std::shared_ptr<std::string> msg)
{
previous_message = 0;
std::cout << *msg << std::endl;
previous_message = msg;
}
Bevor wir mit dem Senden beginnen, verwerfen wir die aktuelle vorherige Nachricht. Nachdem der Versand abgeschlossen ist, können wir die neue vorherige Nachricht speichern. Alles gut. Hier ist ein Testcode:
send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);
Und wie erwartet wird dies gedruckt Hi!
zweimal .
Jetzt kommt Herr Maintainer, der sich den Code ansieht und denkt: Hey, dieser Parameter send_message
ist a shared_ptr
:
void send_message(std::shared_ptr<std::string> msg)
Das kann natürlich geändert werden in:
void send_message(const std::shared_ptr<std::string> &msg)
Denken Sie an die Leistungssteigerung, die dies bringen wird! (Es ist egal, dass wir im Begriff sind, eine normalerweise große Nachricht über einen Kanal zu senden, sodass die Leistungssteigerung so gering ist, dass sie nicht messbar ist.)
Das eigentliche Problem ist jedoch, dass der Testcode jetzt ein undefiniertes Verhalten aufweist (in Debug-Builds von Visual C ++ 2010 stürzt er ab).
Herr Maintainer ist davon überrascht, fügt jedoch eine Verteidigungskontrolle hinzu send_message
, um das Auftreten des Problems zu stoppen:
void send_message(const std::shared_ptr<std::string> &msg)
{
if (msg == 0)
return;
Aber natürlich geht es immer noch weiter und stürzt ab, weil msg
es beim send_message
Aufruf nie null ist.
Wie gesagt, wenn der gesamte Code in einem trivialen Beispiel so nahe beieinander liegt, ist es leicht, den Fehler zu finden. In realen Programmen mit komplexeren Beziehungen zwischen veränderlichen Objekten, die Zeiger aufeinander enthalten, ist dies jedoch einfach zu erstellen den Fehler und die erforderlichen Testfälle zu erstellen, um den Fehler zu erkennen.
Die einfache Lösung, bei der Sie möchten, dass sich eine Funktion darauf verlassen kann, dass sie shared_ptr
weiterhin durchgehend ungleich Null ist, besteht darin, dass die Funktion ihr eigenes true shared_ptr
zuweist, anstatt sich auf einen Verweis auf ein vorhandenes zu verlassen shared_ptr
.
Der Nachteil ist, dass das Kopieren von a shared_ptr
nicht kostenlos ist: Selbst "sperrenfreie" Implementierungen müssen eine ineinandergreifende Operation verwenden, um Threading-Garantien einzuhalten. Es kann also Situationen geben, in denen ein Programm durch Ändern von a shared_ptr
in a erheblich beschleunigt werden kann shared_ptr &
. Dies ist jedoch keine Änderung, die sicher an allen Programmen vorgenommen werden kann. Es ändert die logische Bedeutung des Programms.
Beachten Sie, dass ein ähnlicher Fehler auftreten würde, wenn wir std::string
anstelle von std::shared_ptr<std::string>
und anstelle von:
previous_message = 0;
Um die Nachricht zu löschen, sagten wir:
previous_message.clear();
Dann wäre das Symptom das versehentliche Senden einer leeren Nachricht anstelle eines undefinierten Verhaltens. Die Kosten für eine zusätzliche Kopie einer sehr großen Zeichenfolge können viel höher sein als die Kosten für das Kopieren einer Zeichenfolge shared_ptr
, sodass der Kompromiss unterschiedlich sein kann.