C ++ - Verweise auf std :: shared_ptr oder boost :: shared_ptr übergeben

115

Wenn ich eine Funktion habe, die mit einem arbeiten muss shared_ptr , wäre es nicht effizienter, ihr einen Verweis darauf zu übergeben (um das Kopieren des shared_ptrObjekts zu vermeiden )? Was sind die möglichen schlimmen Nebenwirkungen? Ich stelle mir zwei mögliche Fälle vor:

1) Innerhalb der Funktion wird eine Kopie des Arguments erstellt, wie in

ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)  
{  
     ...  
     m_sp_member=sp; //This will copy the object, incrementing refcount  
     ...  
}  

2) Innerhalb der Funktion wird nur das Argument verwendet, wie in

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}  

Ich kann in beiden Fällen keinen guten Grund sehen, das zu bestehen boost::shared_ptr<foo> by-Wert anstelle des Verweises zu übergeben. Das Übergeben eines Werts würde den Referenzzähler aufgrund des Kopierens nur "vorübergehend" erhöhen und ihn dann beim Verlassen des Funktionsumfangs verringern. Übersehe ich etwas?

Nur um zu verdeutlichen, nachdem ich mehrere Antworten gelesen habe: Ich stimme den Bedenken hinsichtlich vorzeitiger Optimierung vollkommen zu und versuche immer, zuerst das Profil zu erstellen und dann an den Hotspots zu arbeiten. Meine Frage war eher aus rein technischer Code-Sicht, wenn Sie wissen, was ich meine.

abigagli
quelle
Ich weiß nicht, ob Sie die Tags Ihrer Frage ändern können, aber versuchen Sie bitte, dort ein Boost-Tag hinzuzufügen. Ich habe versucht, nach dieser Frage zu suchen, konnte aber keine finden, weil ich nach Boost- und Smart-Pointer-Tags gesucht habe. Also fand ich Ihre Frage gleich nach dem Verfassen meiner eigenen Frage
Edison Gustavo Muenz

Antworten:

113

Der Zweck einer bestimmten shared_ptrInstanz 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_ptrWenn 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_ptrObjekt 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:

  1. Untersuchen Sie die Quelle Ihres gesamten Programms sehr sorgfältig, bis Sie sicher sind, dass das Objekt während des Funktionskörpers nicht stirbt.

  2. Ä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_ptran 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_messageist 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 msges beim send_messageAufruf 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_ptrweiterhin durchgehend ungleich Null ist, besteht darin, dass die Funktion ihr eigenes true shared_ptrzuweist, anstatt sich auf einen Verweis auf ein vorhandenes zu verlassen shared_ptr.

Der Nachteil ist, dass das Kopieren von a shared_ptrnicht 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_ptrin 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::stringanstelle 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.

Daniel Earwicker
quelle
16
Das übergebene shared_ptr befindet sich bereits in einem Bereich am Aufrufstandort. Möglicherweise können Sie ein ausgeklügeltes Szenario erstellen, in dem der Code in dieser Frage aufgrund eines baumelnden Zeigers explodiert, aber dann haben Sie vermutlich größere Probleme als der Referenzparameter!
Magnus Hoff
10
Es kann in einem Mitglied gespeichert werden. Sie können etwas aufrufen, das dieses Mitglied löscht. Der Sinn von smart_ptr besteht darin, zu vermeiden, dass Lebensdauern in Hierarchien oder Bereichen koordiniert werden müssen, die sich um den Aufrufstapel herum befinden. Daher ist es am besten anzunehmen, dass Lebensdauern dies in solchen Programmen nicht tun.
Daniel Earwicker
7
Es ist jedoch nicht wirklich mein Standpunkt! Wenn Sie der Meinung sind, dass das, was ich sage, etwas Spezifisches mit meinem Code zu tun hat, haben Sie mich möglicherweise nicht verstanden. Ich spreche von einer unvermeidbaren Implikation des Grundes, warum shared_ptr überhaupt existiert: Viele Objektlebensdauern hängen nicht einfach mit Funktionsaufrufen zusammen.
Daniel Earwicker
8
@ DanielEarwicker stimmt all Ihren Punkten voll und ganz zu und ist von der Opposition überrascht. Etwas, das Ihre Bedenken noch relevanter macht, ist das Threading, wenn dies involviert wird. Garantien über die Gültigkeit eines Objekts werden viel wichtiger. Gute Antwort.
Radman
3
Vor nicht allzu langer Zeit habe ich einen sehr schwerwiegenden Fehler verfolgt, der darauf zurückzuführen war, dass ein Verweis auf einen gemeinsam genutzten Zeiger übergeben wurde. Der Code behandelte die Statusänderung eines Objekts. Als er feststellte, dass sich der Status des Objekts geändert hatte, entfernte er es aus der Sammlung von Objekten im vorherigen Status und verschob es in die Sammlung von Objekten im neuen Status. Die Entfernungsoperation zerstörte den letzten gemeinsam genutzten Zeiger auf das Objekt. Die Member-Funktion wurde für einen Verweis auf den gemeinsam genutzten Zeiger in der Sammlung aufgerufen. Boom. Daniel Earwicker hat recht.
David Schwartz
115

Ich war mit der Antwort mit der höchsten Stimme nicht einverstanden, also suchte ich nach Expertenmeinungen und hier sind sie. Von http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

Herb Sutter: "Wenn Sie shared_ptrs übergeben, sind Kopien teuer."

Scott Meyers: "Shared_ptr hat nichts Besonderes, wenn es darum geht, ob Sie es als Wert oder als Referenz übergeben. Verwenden Sie genau dieselbe Analyse, die Sie für jeden anderen benutzerdefinierten Typ verwenden. Die Leute scheinen diese Wahrnehmung zu haben, die shared_ptr irgendwie löst." Alle Verwaltungsprobleme, und das, weil es klein ist, ist es notwendigerweise kostengünstig, den Wert weiterzugeben. Es muss kopiert werden, und damit sind Kosten verbunden. Es ist teuer, den Wert weiterzugeben. Wenn ich also damit durchkommen kann Wenn ich die richtige Semantik in meinem Programm habe, werde ich es stattdessen als Verweis auf const oder als Referenz übergeben. "

Herb Sutter: "Übergeben Sie sie immer als Verweis auf const, und gelegentlich, vielleicht weil Sie wissen, was Sie aufgerufen haben, können Sie das ändern, von dem Sie eine Referenz erhalten haben. Vielleicht übergeben Sie sie dann als Wert ... wenn Sie sie als Parameter kopieren, oh Meine Güte, Sie müssen diesen Referenzzähler fast nie erhöhen, weil er sowieso am Leben gehalten wird, und Sie sollten ihn als Referenz weitergeben, also tun Sie das bitte. "

Update: Herb hat dies hier erweitert: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ , obwohl die Moral der Geschichte lautet, dass Sie nicht passieren sollten shared_ptrs überhaupt "es sei denn, Sie möchten den Smart Pointer selbst verwenden oder manipulieren, z. B. um das Eigentum zu teilen oder zu übertragen."

Jon
quelle
8
Schöner Fund! Es ist großartig zu sehen, dass zwei der führenden Experten zu diesem Thema die konventionelle Weisheit über SO öffentlich widerlegen.
Stephan Tolksdorf
3
"Shared_ptr hat nichts Besonderes, wenn es darum geht, ob Sie es als Wert oder als Referenz übergeben" - dem stimme ich wirklich nicht zu. Es ist etwas Besonderes. Persönlich würde ich lieber auf Nummer sicher gehen und den leichten Leistungseinbruch hinnehmen. Wenn es einen bestimmten Codebereich gibt, den ich optimieren muss, dann würde ich mir die Leistungsvorteile von shared_ptr ansehen, das von const ref übergeben wird.
JasonZ
3
Es ist auch interessant festzustellen, dass es zwar eine Einigung über die Überbeanspruchung von shared_ptrs gab, jedoch keine Einigung über das Problem der Übergabe von Wert und Referenz.
Nicol Bolas
2
Scott Meyers: "Wenn ich also mit der richtigen Semantik in meinem Programm davonkommen kann ...", dh widerspricht meiner Antwort überhaupt nicht, was darauf hinweist, dass const &es nur sehr einfach ist , herauszufinden, ob sich das Ändern von Parametern auf die Semantik auswirkt einfache Programme.
Daniel Earwicker
7
Herb Sutter: "Sehr gelegentlich, vielleicht weil Sie wissen, wie Sie es genannt haben, könnte sich das ändern, von dem Sie eine Referenz erhalten haben." Wieder diese kleine Ausnahme für einen kleinen Fall, damit sie meiner Antwort nicht widerspricht. Die Frage bleibt: Woher wissen Sie, dass es sicher ist, eine const ref zu verwenden? Sehr einfach in einem einfachen Programm zu beweisen, nicht so einfach in einem komplexen Programm. Aber hey, das ist C ++, und deshalb bevorzugen wir vorzeitige Mikrooptimierung gegenüber fast allen anderen technischen Belangen, oder?! :)
Daniel Earwicker
22

Ich würde von dieser Praxis abraten, es sei denn, Sie und die anderen Programmierer, mit denen Sie zusammenarbeiten, wissen wirklich , was Sie alle tun.

Erstens haben Sie keine Ahnung, wie sich die Schnittstelle zu Ihrer Klasse entwickeln könnte, und Sie möchten verhindern, dass andere Programmierer schlechte Dinge tun. Das Übergeben eines shared_ptr als Referenz sollte ein Programmierer nicht erwarten, da er nicht idiomatisch ist und es daher einfach ist, ihn falsch zu verwenden. Defensiv programmieren: Machen Sie die Schnittstelle schwer falsch zu bedienen. Das Übergeben als Referenz wird später nur zu Problemen führen.

Zweitens: Optimieren Sie erst, wenn Sie wissen, dass diese bestimmte Klasse ein Problem darstellt. Profil zuerst, und wenn Ihr Programm dann wirklich den Schub braucht, der durch Referenzübergabe gegeben wird, dann vielleicht. Schwitzen Sie andernfalls nicht die kleinen Dinge (dh die zusätzlichen N Anweisungen, die zum Übergeben des Werts erforderlich sind), sondern sorgen Sie sich um Design, Datenstrukturen, Algorithmen und langfristige Wartbarkeit.

Mark Kegel
quelle
Obwohl die Antwort von litb technisch korrekt ist, unterschätzen Sie niemals die "Faulheit" der Programmierer (ich bin auch faul!). Die Antwort von littlenag ist besser, dass ein Verweis auf einen shared_ptr unerwartet ist und möglicherweise (wahrscheinlich) eine unnötige Optimierung, die die zukünftige Wartung schwieriger macht.
Netjeff
18

Ja, eine Referenz ist dort in Ordnung. Sie beabsichtigen nicht, die Methode gemeinsam zu nutzen. es will nur damit arbeiten. Sie können auch für den ersten Fall eine Referenz verwenden, da Sie diese trotzdem kopieren. Aber für den ersten Fall übernimmt es das Eigentum. Es gibt diesen Trick, um es immer noch nur einmal zu kopieren:

void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
    m_sp_member.swap(sp);
}

Sie sollten auch kopieren, wenn Sie es zurückgeben (dh keine Referenz zurückgeben). Weil Ihre Klasse nicht weiß, was der Client damit macht (sie könnte einen Zeiger darauf speichern und dann passiert ein Urknall). Wenn sich später herausstellt, dass es sich um einen Engpass handelt (erstes Profil!), Können Sie trotzdem eine Referenz zurückgeben.


Bearbeiten : Wie andere betonen, gilt dies natürlich nur, wenn Sie Ihren Code kennen und wissen, dass Sie den übergebenen freigegebenen Zeiger nicht auf irgendeine Weise zurücksetzen. Im Zweifelsfall einfach den Wert übergeben.

Johannes Schaub - litb
quelle
11

Es ist sinnvoll, shared_ptrs vorbei zu gehen const&. Es wird wahrscheinlich keine Probleme verursachen (außer in dem unwahrscheinlichen Fall, dass der Verweis shared_ptrwährend des Funktionsaufrufs gelöscht wird, wie von Earwicker beschrieben) und es wird wahrscheinlich schneller sein, wenn Sie viele davon weitergeben. Merken; Die Standardeinstellung boost::shared_ptrist threadsicher, daher enthält das Kopieren ein threadsicheres Inkrement.

Versuchen Sie, const&nicht nur zu verwenden &, da temporäre Objekte möglicherweise nicht als nicht konstante Referenz übergeben werden. (Auch wenn eine Spracherweiterung in MSVC dies trotzdem ermöglicht)

Magnus Hoff
quelle
3
Ja, ich verwende immer const-Referenzen. Ich habe nur vergessen, sie in mein Beispiel aufzunehmen. Auf jeden Fall erlaubt MSVC das Binden von nicht konstanten Verweisen an temporäre Elemente, nicht wegen eines Fehlers, sondern weil standardmäßig die Eigenschaft "C / C ++ -> Sprache -> Spracherweiterung deaktivieren" auf "NEIN" gesetzt ist. Aktivieren Sie es und es wird sie nicht kompilieren ...
abigagli
abigagli: Ernsthaft? Süss! Ich werde dies morgen bei der Arbeit durchsetzen;)
Magnus Hoff
10

Im zweiten Fall ist dies einfacher:

Class::only_work_with_sp(foo &sp)
{    
    ...  
    sp.do_something();  
    ...  
}

Sie können es als nennen

only_work_with_sp(*sp);
Greg Rogers
quelle
3
Wenn Sie die Konvention zur Verwendung von Objektreferenzen übernehmen, wenn Sie keine Kopie des Zeigers erstellen müssen, dient dies auch dazu, Ihre Absicht zu dokumentieren. Es gibt Ihnen auch die Möglichkeit, eine konstante Referenz zu verwenden.
Mark Ransom
Ja, ich stimme der Verwendung von Verweisen auf Objekte zu, um auszudrücken, dass sich die aufgerufene Funktion an nichts über dieses Objekt "erinnert". Normalerweise verwende ich formale Zeigerargumente, wenn die Funktion das Objekt "verfolgt"
abigagli
3

Ich würde eine "einfache" Referenz vermeiden, wenn die Funktion den Zeiger nicht explizit ändern könnte.

A const &kann eine sinnvolle Mikrooptimierung sein, wenn kleine Funktionen aufgerufen werden - z. B. um weitere Optimierungen zu ermöglichen, z. B. um einige Bedingungen aufzuheben. Außerdem ist das Inkrementieren / Dekrementieren - da es threadsicher ist - ein Synchronisationspunkt. Ich würde jedoch nicht erwarten, dass dies in den meisten Szenarien einen großen Unterschied macht.

Im Allgemeinen sollten Sie den einfacheren Stil verwenden, es sei denn, Sie haben Grund, dies nicht zu tun. Verwenden Sie dann entweder das const &konsistente oder fügen Sie einen Kommentar hinzu, warum, wenn Sie es nur an wenigen Stellen verwenden.

peterchen
quelle
3

Ich würde empfehlen, einen gemeinsam genutzten Zeiger als const-Referenz zu übergeben - eine Semantik, dass die mit dem Zeiger übergebene Funktion NICHT den Zeiger besitzt, was für Entwickler eine saubere Redewendung ist.

Die einzige Gefahr besteht darin, dass in mehreren Thread-Programmen das Objekt, auf das der gemeinsam genutzte Zeiger zeigt, in einem anderen Thread zerstört wird. Es ist also sicher zu sagen, dass die Verwendung der const-Referenz des gemeinsam genutzten Zeigers in einem Single-Thread-Programm sicher ist.

Das Übergeben eines gemeinsam genutzten Zeigers als nicht konstante Referenz ist manchmal gefährlich. Der Grund dafür sind die Swap- und Reset-Funktionen, die die Funktion möglicherweise im Inneren aufruft, um das Objekt zu zerstören, das nach der Rückkehr der Funktion immer noch als gültig angesehen wird.

Ich denke, es geht nicht um vorzeitige Optimierung - es geht darum, unnötige Verschwendung von CPU-Zyklen zu vermeiden, wenn Sie klar sind, was Sie tun möchten, und die Codierungssprache von Ihren Mitentwicklern fest übernommen wurde.

Nur meine 2 Cent :-)

zmqiu
quelle
1
Siehe den obigen Kommentar von David Schwartz. "... Ich habe einen sehr schwerwiegenden Fehler aufgespürt, der darauf zurückzuführen war, dass ein Verweis auf einen gemeinsam genutzten Zeiger übergeben wurde. Es entfernte es aus der Sammlung von Objekten im vorherigen Zustand und verschob es in die Sammlung von Objekten im neuen Zustand. Die Entfernungsoperation zerstörte den letzten gemeinsam genutzten Zeiger auf das Objekt. Die Elementfunktion wurde für einen Verweis auf den gemeinsam genutzten Zeiger aufgerufen in der Sammlung. Boom ... "
Jason Harrison
3

Es scheint, dass alle Vor- und Nachteile hier tatsächlich auf JEDEN Typ verallgemeinert werden können, der als Referenz übergeben wird, nicht nur shared_ptr. Meiner Meinung nach sollten Sie die Semantik der Übergabe von Referenz, Konstante und Wert kennen und richtig verwenden. Aber es ist absolut nichts an sich falsch, shared_ptr als Referenz zu übergeben, es sei denn, Sie denken, dass alle Referenzen schlecht sind ...

Um zum Beispiel zurückzukehren:

Class::only_work_with_sp( foo &sp ) //Again, no copy here  
{    
    ...  
    sp.do_something();  
    ...  
}

Woher weißt du, dass sp.do_something()das nicht durch einen baumelnden Zeiger explodiert?

Die Wahrheit ist, dass, ob shared_ptr oder nicht, const oder nicht, dies passieren kann, wenn Sie einen Konstruktionsfehler haben, z. B. direkt oder indirekt das Eigentum spzwischen Threads teilen, ein Objekt missbrauchen, das dies tut delete this, Sie ein zirkuläres Eigentum oder andere Eigentumsfehler haben.

Sandig
quelle
2

Eine Sache, die ich noch nicht erwähnt habe, ist, dass Sie beim Übergeben von gemeinsam genutzten Zeigern als Referenz die implizite Konvertierung verlieren, die Sie erhalten, wenn Sie einen abgeleiteten gemeinsam genutzten Klassenzeiger über einen Verweis auf einen gemeinsam genutzten Basisklassenzeiger übergeben möchten.

Dieser Code erzeugt beispielsweise einen Fehler, funktioniert jedoch, wenn Sie ihn test()so ändern , dass der gemeinsam genutzte Zeiger nicht als Referenz übergeben wird.

#include <boost/shared_ptr.hpp>

class Base { };
class Derived: public Base { };

// ONLY instances of Base can be passed by reference.  If you have a shared_ptr
// to a derived type, you have to cast it manually.  If you remove the reference
// and pass the shared_ptr by value, then the cast is implicit so you don't have
// to worry about it.
void test(boost::shared_ptr<Base>& b)
{
    return;
}

int main(void)
{
    boost::shared_ptr<Derived> d(new Derived);
    test(d);

    // If you want the above call to work with references, you will have to manually cast
    // pointers like this, EVERY time you call the function.  Since you are creating a new
    // shared pointer, you lose the benefit of passing by reference.
    boost::shared_ptr<Base> b = boost::dynamic_pointer_cast<Base>(d);
    test(b);

    return 0;
}
Malvineous
quelle
1

Ich gehe davon aus, dass Sie mit vorzeitiger Optimierung vertraut sind sind und dies entweder für akademische Zwecke oder weil Sie bereits vorhandenen Code isoliert haben, der nicht ausreichend funktioniert.

Das Übergeben als Referenz ist in Ordnung

Das Übergeben einer konstanten Referenz ist besser und kann normalerweise verwendet werden, da es dem Objekt, auf das gezeigt wird, keine Konstanz aufzwingt.

Es besteht keine Gefahr, dass Sie den Zeiger aufgrund der Verwendung einer Referenz verlieren. Diese Referenz ist ein Beweis dafür, dass Sie eine Kopie des Smart Pointers früher im Stapel haben und nur ein Thread einen Aufrufstapel besitzt, sodass eine bereits vorhandene Kopie nicht verschwindet.

Die Verwendung von Referenzen ist aus den von Ihnen genannten Gründen häufig effizienter, jedoch nicht garantiert . Denken Sie daran, dass das Dereferenzieren eines Objekts auch Arbeit erfordern kann. Ihr ideales Referenzverwendungsszenario wäre, wenn Ihr Codierungsstil viele kleine Funktionen umfasst, bei denen der Zeiger vor der Verwendung von Funktion zu Funktion zu Funktion übergeben wird.

Sie sollten es immer vermeiden, Ihren Smart Pointer als Referenz zu speichern . Ihr Class::take_copy_of_sp(&sp)Beispiel zeigt die korrekte Verwendung dafür.

Drew Dormann
quelle
1
"Sie sind nicht gefährdet, den Zeiger aufgrund der Verwendung einer Referenz zu verlieren. Diese Referenz ist ein Beweis dafür, dass Sie eine Kopie des intelligenten Zeigers früher im Stapel haben." Oder ein Datenelement ...?
Daniel Earwicker
Betrachten Sie die Magie von boost :: thread und boost :: ref: boost :: function <int> functionPointer = boost :: bind (doSomething, boost :: ref (sharedPtrInstance)); m_workerThread = neuer boost :: thread (functionPointer); ... löschen sharedPtrInstance
Jason Harrison
1

Angenommen, es geht uns nicht um die Richtigkeit von Konstanten (oder mehr, Sie möchten, dass die Funktionen die übergebenen Daten ändern oder deren Besitz teilen können), ist das Übergeben eines boost :: shared_ptr nach Wert sicherer als das Übergeben als Referenz als Wir erlauben dem ursprünglichen boost :: shared_ptr, seine eigene Lebensdauer zu steuern. Betrachten Sie die Ergebnisse des folgenden Codes ...

void FooTakesReference( boost::shared_ptr< int > & ptr )
{
    ptr.reset(); // We reset, and so does sharedA, memory is deleted.
}

void FooTakesValue( boost::shared_ptr< int > ptr )
{
    ptr.reset(); // Our temporary is reset, however sharedB hasn't.
}

void main()
{
    boost::shared_ptr< int > sharedA( new int( 13 ) );
    boost::shared_ptr< int > sharedB( new int( 14 ) );

    FooTakesReference( sharedA );

    FooTakesValue( sharedB );
}

Aus dem obigen Beispiel sehen wir , dass Passieren sharedA durch Bezugnahme erlaubt FooTakesReference die ursprünglichen Zeiger zurückgesetzt werden , die es der Verwendungszähler auf 0 reduziert, es die Daten zu zerstören. FooTakesValue kann den ursprünglichen Zeiger jedoch nicht zurücksetzen, wodurch sichergestellt wird, dass die Daten von sharedB weiterhin verwendet werden können. Wenn ein anderer Entwickler unweigerlich vorbeikommt und versucht, auf die fragile Existenz von sharedA zurückzugreifen , entsteht Chaos. Der glückliche SharedB- Entwickler geht jedoch früh nach Hause, da in seiner Welt alles in Ordnung ist.

Die Codesicherheit überwiegt in diesem Fall bei weitem die durch das Kopieren verursachte Geschwindigkeitsverbesserung. Gleichzeitig soll boost :: shared_ptr die Codesicherheit verbessern. Es wird viel einfacher sein, von einer Kopie zu einer Referenz zu wechseln, wenn etwas diese Art der Nischenoptimierung erfordert.

Kit10
quelle
1

Sandy schrieb: "Es scheint, dass alle Vor- und Nachteile hier tatsächlich auf JEDEN Typ verallgemeinert werden können, der als Referenz übergeben wird, nicht nur shared_ptr."

Bis zu einem gewissen Grad wahr, aber der Sinn der Verwendung von shared_ptr besteht darin, Bedenken hinsichtlich der Objektlebensdauer zu beseitigen und den Compiler dies für Sie erledigen zu lassen. Wenn Sie einen gemeinsam genutzten Zeiger als Referenz übergeben und Clients Ihres referenzgezählten Objekts erlauben, nicht konstante Methoden aufzurufen, die die Objektdaten freigeben könnten, ist die Verwendung eines gemeinsam genutzten Zeigers fast sinnlos.

Ich habe in diesem vorherigen Satz "fast" geschrieben, weil Leistung ein Problem sein kann und in seltenen Fällen "gerechtfertigt" sein könnte, aber ich würde dieses Szenario auch selbst vermeiden und selbst nach allen möglichen anderen Optimierungslösungen suchen, um ernsthaft nachzuschauen beim Hinzufügen einer weiteren Ebene der Indirektion, faulen Bewertung usw ..

Code, der über den Autor hinaus existiert oder sogar das Gedächtnis des Autors veröffentlicht, der implizite Annahmen über das Verhalten, insbesondere das Verhalten über die Objektlebensdauer, erfordert, erfordert eine klare, präzise und lesbare Dokumentation, und dann lesen ihn viele Clients sowieso nicht! Einfachheit übertrifft fast immer die Effizienz, und es gibt fast immer andere Möglichkeiten, effizient zu sein. Wenn Sie wirklich Werte als Referenz übergeben müssen, um ein tiefes Kopieren Ihrer Objekte mit Referenzzählung (und des Operators equals) durch Kopierkonstruktoren zu vermeiden, sollten Sie möglicherweise überlegen, wie Sie die tief kopierten Daten zu Zeigern mit Referenzzählung machen können schnell kopiert. (Dies ist natürlich nur ein Entwurfsszenario, das möglicherweise nicht auf Ihre Situation zutrifft.)

Bill H.
quelle
1

Ich habe in einem Projekt gearbeitet, bei dem das Prinzip sehr stark darin bestand, intelligente Zeiger nach Wert zu übergeben. Als ich gebeten wurde, eine Leistungsanalyse durchzuführen, stellte ich fest, dass die Anwendung zum Inkrementieren und Dekrementieren der Referenzzähler der intelligenten Zeiger zwischen 4 und 6% der verwendeten Prozessorzeit verbringt.

Wenn Sie die intelligenten Zeiger nach Wert übergeben möchten, um Probleme in seltsamen Fällen zu vermeiden, wie von Daniel Earwicker beschrieben, stellen Sie sicher, dass Sie den Preis verstehen, den Sie dafür zahlen.

Wenn Sie sich für eine Referenz entscheiden, besteht der Hauptgrund für die Verwendung der Konstantenreferenz darin, implizites Upcasting zu ermöglichen, wenn Sie einen gemeinsam genutzten Zeiger an ein Objekt aus einer Klasse übergeben müssen, die die in der Schnittstelle verwendete Klasse erbt.

gsf
quelle
0

Zusätzlich zu dem, was litb gesagt hat, möchte ich darauf hinweisen, dass es wahrscheinlich darum geht, im zweiten Beispiel eine konstante Referenz zu verwenden, damit Sie sicher sind, dass Sie es nicht versehentlich ändern.

Leon Timmermans
quelle
0
struct A {
  shared_ptr<Message> msg;
  shared_ptr<Message> * ptr_msg;
}
  1. Wert übergeben:

    void set(shared_ptr<Message> msg) {
      this->msg = msg; /// create a new shared_ptr, reference count will be added;
    } /// out of method, new created shared_ptr will be deleted, of course, reference count also be reduced;
  2. als Referenz übergeben:

    void set(shared_ptr<Message>& msg) {
     this->msg = msg; /// reference count will be added, because reference is just an alias.
     }
  3. Zeiger übergeben:

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }
Dylan Chen
quelle
0

Jedes Code-Stück muss einen Sinn haben. Wenn Sie überall in der Anwendung einen gemeinsam genutzten Zeiger als Wert übergeben , bedeutet dies "Ich bin mir nicht sicher, was anderswo vor sich geht, daher bevorzuge ich die Sicherheit ". Dies ist kein gutes Vertrauenszeichen für andere Programmierer, die den Code konsultieren könnten.

Selbst wenn eine Funktion eine konstante Referenz erhält und Sie "unsicher" sind, können Sie dennoch eine Kopie des gemeinsam genutzten Zeigers am Kopf der Funktion erstellen, um dem Zeiger eine starke Referenz hinzuzufügen. Dies könnte auch als Hinweis auf das Design angesehen werden ("der Zeiger könnte an anderer Stelle geändert werden").

Also ja, IMO, der Standard sollte " Pass by Const Reference " sein.

Philippe
quelle