Gibt intelligente Zeiger nach Wert zurück.
Wie Sie bereits gesagt haben, erhöhen Sie den Referenzzähler nicht richtig, wenn Sie ihn als Referenz zurückgeben, was das Risiko erhöht, dass etwas zum falschen Zeitpunkt gelöscht wird. Das allein sollte Grund genug sein, nicht per Referenz zurückzukehren. Schnittstellen sollten robust sein.
Das Kostenproblem ist heutzutage dank der Rückgabewertoptimierung (RVO) umstritten, sodass bei modernen Compilern keine Inkrement-Inkrement-Dekrement-Sequenz oder ähnliches auftritt. Der beste Weg, a zurückzugeben, shared_ptr
besteht darin, einfach nach Wert zurückzugeben:
shared_ptr<T> Foo()
{
return shared_ptr<T>(/* acquire something */);
};
Dies ist eine absolut offensichtliche RVO-Möglichkeit für moderne C ++ - Compiler. Ich weiß, dass Visual C ++ - Compiler RVO auch dann implementieren, wenn alle Optimierungen deaktiviert sind. Und mit der Verschiebungssemantik von C ++ 11 ist dieses Problem noch weniger relevant. (Der einzige Weg, um sicher zu sein, ist das Profilieren und Experimentieren.)
Wenn Sie immer noch nicht überzeugt sind, hat Dave Abrahams einen Artikel , der ein Argument für die Rückkehr nach Wert liefert. Ich reproduziere hier einen Ausschnitt; Ich empfehle Ihnen dringend, den gesamten Artikel zu lesen:
Seien Sie ehrlich: Wie fühlen Sie sich mit dem folgenden Code?
std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();
Ehrlich gesagt, obwohl ich es besser wissen sollte, macht es mich nervös. Grundsätzlich müssen get_names()
wir bei der Rücksendung a vector
von string
s kopieren . Dann müssen wir es bei der Initialisierung erneut kopieren
names
und die erste Kopie zerstören. Wenn string
der Vektor N s enthält, kann jede Kopie bis zu N + 1 Speicherzuweisungen und eine ganze Reihe von cache-unfreundlichen Datenzugriffen erfordern,> wenn der String-Inhalt kopiert wird.
Anstatt mich dieser Art von Angst zu stellen, habe ich oft auf Pass-by-Reference zurückgegriffen, um unnötige Kopien zu vermeiden:
get_names(std::vector<std::string>& out_param );
...
std::vector<std::string> names;
get_names( names );
Leider ist dieser Ansatz alles andere als ideal.
- Der Code wuchs um 150%
- Wir mussten
const
-ness fallen lassen, weil wir Namen mutieren.
- Wie funktionale Programmierer uns gerne daran erinnern, macht die Mutation die Argumentation von Code komplexer, indem sie die referenzielle Transparenz und das Argumentationsgleich untergräbt.
- Wir haben keine strikte Wertesemantik mehr für Namen.
Aber ist es wirklich notwendig, unseren Code auf diese Weise durcheinander zu bringen, um die Effizienz zu steigern? Glücklicherweise lautet die Antwort nein (und insbesondere nicht, wenn Sie C ++ 0x verwenden).
cout << "Hello World!";
Anweisung in einem Standard- und Kopierkonstruktor haben, werdenHello World!
bei aktivem RVO keine zwei s angezeigt . Dies sollte jedoch kein Problem für richtig gestaltete intelligente Zeiger sein, selbst für die Synchronisation.In Bezug auf einen intelligenten Zeiger (nicht nur shared_ptr) halte ich es nicht für akzeptabel, einen Verweis auf einen zurückzugeben, und ich würde sehr zögern, sie als Referenz oder Rohzeiger weiterzugeben. Warum? Weil Sie nicht sicher sein können, dass es später nicht über eine Referenz flach kopiert wird. Ihr erster Punkt definiert den Grund, warum dies ein Problem sein sollte. Dies kann sogar in einer Umgebung mit einem Thread geschehen. Sie benötigen keinen gleichzeitigen Zugriff auf Daten, um eine Semantik für fehlerhafte Kopien in Ihre Programme aufzunehmen. Sie steuern nicht wirklich, was Ihre Benutzer mit dem Zeiger tun, wenn Sie ihn weitergeben. Ermutigen Sie daher nicht zu Missbrauch, indem Sie Ihren API-Benutzern genügend Seil geben, um sich aufzuhängen.
Zweitens sollten Sie sich nach Möglichkeit die Implementierung Ihres Smart Pointers ansehen. Bau und Zerstörung sollten nahezu vernachlässigbar sein. Wenn dieser Overhead nicht akzeptabel ist, verwenden Sie keinen intelligenten Zeiger! Darüber hinaus müssen Sie jedoch auch die Parallelitätsarchitektur untersuchen, die Sie haben, da der sich gegenseitig ausschließende Zugriff auf den Mechanismus, der die Verwendung des Zeigers verfolgt, Sie mehr als nur die Konstruktion des shared_ptr-Objekts verlangsamen wird.
Bearbeiten, 3 Jahre später: Mit dem Aufkommen der moderneren Funktionen in C ++ würde ich meine Antwort dahingehend optimieren, dass Fälle akzeptiert werden, in denen Sie einfach ein Lambda geschrieben haben, das niemals außerhalb des Bereichs der aufrufenden Funktion lebt und dies nicht ist woanders kopiert. Wenn Sie hier den minimalen Aufwand für das Kopieren eines gemeinsam genutzten Zeigers sparen möchten, ist dies fair und sicher. Warum? Weil Sie garantieren können, dass die Referenz niemals missbraucht wird.
quelle