Wie gebe ich intelligente Zeiger (shared_ptr) als Referenz oder nach Wert zurück?

94

Angenommen, ich habe eine Klasse mit einer Methode, die a zurückgibt shared_ptr.

Was sind die möglichen Vor- und Nachteile einer Rücksendung nach Referenz oder Wert?

Zwei mögliche Hinweise:

  • Frühe Objektzerstörung. Wenn ich die shared_ptrby (const) -Referenz zurückgebe, wird der Referenzzähler nicht inkrementiert, sodass das Risiko besteht, dass das Objekt gelöscht wird, wenn es in einem anderen Kontext (z. B. einem anderen Thread) den Gültigkeitsbereich verlässt. Ist das richtig? Was kann auch passieren, wenn die Umgebung Single-Threaded ist?
  • Kosten. Pass-by-Value ist sicherlich nicht kostenlos. Lohnt es sich, es wann immer möglich zu vermeiden?

Danke an alle.

Vincenzo Pii
quelle

Antworten:

114

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_ptrbesteht 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 vectorvon strings kopieren . Dann müssen wir es bei der Initialisierung erneut kopieren namesund die erste Kopie zerstören. Wenn stringder 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).

In silico
quelle
Ich weiß nicht, dass ich sagen würde, dass RVO die Frage streitig macht, da die Rückkehr durch Bezugnahme RVO entschieden unmöglich macht.
Edward Strange
@CrazyEddie: Stimmt, das ist einer der Gründe, warum ich empfehle, dass das OP nach Wert zurückgibt.
In silico
Trumpft die vom Standard zugelassene RVO-Regel die vom Standard garantierten Regeln für Synchronisation / Vor-Vor-Beziehungen?
edA-qa mort-ora-y
1
@ edA-qa mort-ora-y: RVO ist ausdrücklich erlaubt, auch wenn es Nebenwirkungen hat. Wenn Sie beispielsweise eine cout << "Hello World!";Anweisung in einem Standard- und Kopierkonstruktor haben, werden Hello 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 silico
23

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.

San Jacinto
quelle