webentwicklung-frage-antwort-db.com.de

Sollten wir einen shared_ptr als Referenz oder als Wert übergeben?

Wenn eine Funktion einen shared_ptr (Von Boost oder C++ 11 STL) annimmt, übergeben Sie ihn:

  • durch konstante Referenz: void foo(const shared_ptr<T>& p)

  • oder nach Wert: void foo(shared_ptr<T> p)?

Ich würde die erste Methode vorziehen, weil ich vermute, dass sie schneller sein würde. Aber lohnt sich das wirklich oder gibt es zusätzliche Probleme?

Könnten Sie bitte die Gründe für Ihre Wahl angeben oder, falls dies der Fall ist, warum Sie glauben, dass dies keine Rolle spielt?.

240
Danvil

Diese Frage wurde von Scott, Andrei und Herb während der Ask Us Anything Sitzung bei C++ und darüber hinaus 2011 diskutiert und beantwortet. Beobachten von 4min 34sek auf shared_ptr Leistung und Richtigkeit .

Kurz gesagt, es gibt keinen Grund, den Wert zu überschreiten , es sei denn, das Ziel besteht darin, den Besitz eines Objekts zu teilen (z. B. zwischen verschiedenen Datenstrukturen oder zwischen verschiedenen Threads).

Es sei denn, Sie können es verschieben und optimieren, wie von Scott Meyers im oben verlinkten Talk-Video erläutert. Dies hängt jedoch mit der aktuellen Version von C++ zusammen, die Sie verwenden können.

Ein wichtiges Update dieser Diskussion wurde während der GoingNative 2012 Konferenz Interaktives Panel: Ask Us Anything! vorgenommen, das es wert ist, angesehen zu werden, insbesondere ab 22:5 .

203
mloskot

Hier ist Herb Sutters Einstellung

Richtlinie: Übergeben Sie keinen intelligenten Zeiger als Funktionsparameter, es sei denn, Sie möchten den intelligenten Zeiger selbst verwenden oder bearbeiten, z. B. um das Eigentum zu teilen oder zu übertragen.

Richtlinie: Drücken Sie aus, dass eine Funktion den Besitz eines Heap-Objekts mithilfe eines wertweisen shared_ptr-Parameters speichert und teilt.

Richtlinie: Verwenden Sie nur einen nicht konstanten shared_ptr & -Parameter, um den shared_ptr zu ändern. Verwenden Sie eine const shared_ptr & nur dann als Parameter, wenn Sie nicht sicher sind, ob Sie eine Kopie erstellen und den Besitz teilen möchten. Verwenden Sie andernfalls stattdessen widget * (oder, falls nicht nullbar, widget &).

78
acel

Persönlich würde ich eine const Referenz verwenden. Es ist nicht erforderlich, den Referenzzähler zu erhöhen, nur um ihn für einen Funktionsaufruf erneut zu dekrementieren.

60
Evan Teran

Übergeben Sie die Referenz const, sie ist schneller. Wenn Sie es aufbewahren müssen, sagen Sie in einem Behälter, der ref. Die Anzahl wird durch den Kopiervorgang automatisch inkrementiert.

34

Ich habe den folgenden Code einmal ausgeführt, wobei foo den shared_ptr durch const& und wieder mit foo, wobei shared_ptr nach Wert.

void foo(const std::shared_ptr<int>& p)
{
    static int x = 0;
    *p = ++x;
}

int main()
{
    auto p = std::make_shared<int>();
    auto start = clock();
    for (int i = 0; i < 10000000; ++i)
    {
        foo(p);
    }    
    std::cout << "Took " << clock() - start << " ms" << std::endl;
}

Mit VS2015, x86 Release Build, auf meinem Intel Core 2 Quad (2,4 GHz) Prozessor

const shared_ptr&     - 10ms  
shared_ptr            - 281ms 

Die Wertkopie war um eine Größenordnung langsamer.
Wenn Sie eine Funktion synchron vom aktuellen Thread aus aufrufen, bevorzugen Sie das const& Ausführung.

20
tcb

Seit C++ 11 sollten Sie es nach Wert über const & öfter nehmen, als Sie vielleicht denken.

Wenn Sie std :: shared_ptr (anstelle des zugrunde liegenden Typs T) verwenden, tun Sie dies, weil Sie etwas damit tun möchten.

Wenn Sie m es zu kopieren irgendwo möchten, ist es sinnvoller, es als Kopie zu nehmen und std :: intern zu verschieben, anstatt es mit const & zu nehmen und später zu kopieren. Dies liegt daran, dass Sie dem Aufrufer die Option erlauben, beim Aufrufen Ihrer Funktion std :: move the shared_ptr auszuführen, um sich eine Reihe von Inkrementierungs- und Dekrementierungsoperationen zu ersparen. Oder nicht. Das heißt, der Aufrufer der Funktion kann entscheiden, ob er nach dem Aufrufen der Funktion std :: shared_ptr benötigt oder nicht, und dies hängt davon ab, ob er sich bewegt oder nicht. Dies ist nicht erreichbar, wenn Sie const & übergeben, und daher ist es dann vorzuziehen, es als Wert zu nehmen.

Natürlich, wenn der Aufrufer beide seinen shared_ptr länger benötigt (also nicht std :: move kann) und Sie keine einfache Kopie in der Funktion erstellen möchten (sagen wir, Sie möchten einen schwachen Zeiger, oder Sie möchten nur manchmal) Um es zu kopieren, ist je nach Bedingung eine Konstante vorzuziehen.

Zum Beispiel sollten Sie tun

void enqueue(std::shared<T> t) m_internal_queue.enqueue(std::move(t));

über

void enqueue(std::shared<T> const& t) m_internal_queue.enqueue(t);

Denn in diesem Fall erstellen Sie intern immer eine Kopie

13
Cookie

Da ich die Zeitkosten für den Kopiervorgang shared_copy nicht kenne, bei dem die atomare Inkrementierung und Dekrementierung erfolgt, litt ich unter einem viel höheren CPU-Nutzungsproblem. Ich hätte nie gedacht, dass ein atomares Inkrementieren und Dekrementieren so viel kosten würde.

Nach meinem Testergebnis dauert das Inkrementieren und Dekrementieren von int32-Atomen zwei- oder vierzigmal so lange wie das Inkrementieren und Dekrementieren von Nicht-Atomen. Ich habe es auf 3GHz Core i7 mit Windows 8.1. Das erstere Ergebnis wird angezeigt, wenn keine Konflikte auftreten, das letztere, wenn eine hohe Wahrscheinlichkeit von Konflikten besteht. Ich denke daran, dass atomare Operationen letztendlich hardwarebasierte Sperren sind. Sperre ist Sperre. Schlechte Leistung, wenn Konflikte auftreten.

In diesem Fall verwende ich immer byref (const shared_ptr &) als byval (shared_ptr).

1
Hyunjik Bae