Wie kann ich std :: unique_ptr an eine Funktion übergeben?

97

Wie kann ich ein std::unique_ptrin eine Funktion übergeben? Nehmen wir an, ich habe folgende Klasse:

class A
{
public:
    A(int val)
    {
        _val = val;
    }

    int GetVal() { return _val; }
private:
    int _val;
};

Folgendes wird nicht kompiliert:

void MyFunc(unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(ptr);

    return 0;
}

Warum kann ich a nicht an std::unique_ptreine Funktion übergeben? Dies ist sicherlich der Hauptzweck des Konstrukts? Oder wollte das C ++ - Komitee, dass ich auf rohe C-Zeiger zurückgreife und es so weitergebe:

MyFunc(&(*ptr)); 

Und am seltsamsten ist, warum ist dies eine gute Möglichkeit, es weiterzugeben? Es scheint schrecklich inkonsistent:

MyFunc(unique_ptr<A>(new A(1234)));
user3690202
quelle
7
Es ist nichts Falsches daran, auf rohe Zeiger im C-Stil zurückzugreifen, solange sie keine rohen Zeiger besitzen. Vielleicht möchten Sie lieber 'ptr.get ()' schreiben. Wenn Sie jedoch keine Nullfähigkeit benötigen, wird eine Referenz bevorzugt.
Chris Drew

Antworten:

142

Grundsätzlich gibt es hier zwei Möglichkeiten:

Übergeben Sie den Smart Pointer als Referenz

void MyFunc(unique_ptr<A> & arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(ptr);
}

Bewegen Sie den Smart Pointer in das Funktionsargument

Beachten Sie, dass in diesem Fall die Behauptung gilt!

void MyFunc(unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(move(ptr));
    assert(ptr == nullptr)
}
Bill Lynch
quelle
@VermillionAzure: Die Funktion, auf die Sie sich beziehen, wird normalerweise als nicht kopierbar und nicht eindeutig bezeichnet.
Bill Lynch
1
Vielen Dank. In diesem Fall wollte ich eine Instanz erstellen, einige Aktionen ausführen und dann den Besitz an eine andere Person weitergeben, für die move () perfekt erscheint.
user3690202
7
Sie sollten eine unique_ptrReferenz nur übergeben, wenn sich die Funktion möglicherweise von ihr entfernt oder nicht. Und dann sollte es eine Wertreferenz sein. Verwenden Sie eine Referenz wie A const&oder, um ein Objekt zu beobachten, ohne dass etwas über seine Besitzersemantik erforderlich ist A&.
Potatoswatter
11
Hören Sie sich Herb Sutters Vortrag auf der CppCon 2014 an. Er rät dringend davon ab, Verweise auf unique_ptr <> zu übergeben. Das Wesentliche ist, dass Sie nur intelligente Zeiger wie unique_ptr <> oder shared_ptr <> verwenden sollten, wenn Sie sich mit dem Eigentum befassen. Siehe www.youtube.com/watch?v=xnqTKD8uD64 Hier finden Sie die Folien github.com/CppCon/CppCon2014/tree/master/Presentations/…
schorsch_76
2
@Furihr: Es ist nur eine Möglichkeit zu zeigen, was der Wert von ptrnach dem Umzug ist.
Bill Lynch
26

Sie übergeben es als Wert, was bedeutet, dass Sie eine Kopie erstellen. Das wäre doch nicht sehr einzigartig, oder?

Sie können den Wert verschieben, dies bedeutet jedoch, dass das Eigentum an dem Objekt und die Kontrolle über seine Lebensdauer an die Funktion übergeben werden.

Wenn die Lebensdauer des Objekts während der gesamten Lebensdauer des Aufrufs von MyFunc garantiert ist, übergeben Sie einfach einen Rohzeiger über ptr.get().

Mark Tolonen
quelle
17

Warum kann ich a nicht an unique_ptreine Funktion übergeben?

Sie können dies nicht tun, da unique_ptres einen Verschiebungskonstruktor, aber keinen Kopierkonstruktor gibt. Gemäß dem Standard wird der Kopierkonstruktor gelöscht, wenn ein Verschiebungskonstruktor definiert ist, ein Kopierkonstruktor jedoch nicht definiert ist.

12.8 Kopieren und Verschieben von Klassenobjekten

...

7 Wenn die Klassendefinition einen Kopierkonstruktor nicht explizit deklariert, wird einer implizit deklariert. Wenn die Klassendefinition einen Verschiebungskonstruktor oder einen Verschiebungszuweisungsoperator deklariert, wird der implizit deklarierte Kopierkonstruktor als gelöscht definiert.

Sie können das unique_ptran die Funktion übergeben, indem Sie Folgendes verwenden:

void MyFunc(std::unique_ptr<A>& arg)
{
    cout << arg->GetVal() << endl;
}

und benutze es wie du:

oder

void MyFunc(std::unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

und benutze es wie:

std::unique_ptr<A> ptr = std::unique_ptr<A>(new A(1234));
MyFunc(std::move(ptr));

Wichtige Notiz

Beachten Sie, dass bei Verwendung der zweiten Methode ptrder Zeiger nach dem Aufruf von std::move(ptr)return nicht im Besitz des Zeigers ist .

void MyFunc(std::unique_ptr<A>&& arg)hätte den gleichen Effekt wie void MyFunc(std::unique_ptr<A>& arg)da beide Referenzen sind.

Im ersten Fall ptrhat der Zeiger nach dem Aufruf von noch Besitz MyFunc.

R Sahu
quelle
5

Da MyFunces kein Eigentum übernimmt, wäre es besser, Folgendes zu haben:

void MyFunc(const A* arg)
{
    assert(arg != nullptr); // or throw ?
    cout << arg->GetVal() << endl;
}

oder besser

void MyFunc(const A& arg)
{
    cout << arg.GetVal() << endl;
}

Wenn Sie wirklich Eigentümer werden möchten, müssen Sie Ihre Ressource verschieben:

std::unique_ptr<A> ptr = std::make_unique<A>(1234);
MyFunc(std::move(ptr));

oder übergeben Sie direkt eine r-Wert-Referenz:

MyFunc(std::make_unique<A>(1234));

std::unique_ptr hat nicht absichtlich eine Kopie, um zu garantieren, nur einen Besitzer zu haben.

Jarod42
quelle
In dieser Antwort fehlt, wie der Aufruf von MyFunc für Ihre ersten beiden Fälle aussieht. Sie sind sich nicht sicher, ob Sie die Funktion verwenden ptr.get()oder nur übergeben ptr?
Taylorstine
Was ist die beabsichtigte Signatur MyFuncfür die Übergabe einer R-Wert-Referenz?
James Hirschorn
@ JamesHirschorn: void MyFunc(A&& arg)nimmt R-Wert Referenz ...
Jarod42
@ Jarod42 Das habe ich erraten, aber mit typedef int A[], MyFunc(std::make_unique<A>(N))gibt der Compiler Fehler: Fehler: Ungültige Initialisierung der Referenz vom Typ 'int (&&) []' aus dem Ausdruck vom Typ 'std :: _ MakeUniq <int []> :: __ array' { aka 'std :: unique_ptr <int [], std :: default_delete <int [] >>'} Ist g++ -std=gnu++11neu genug?
James Hirschorn
@ Jarod42 Ich habe einen Fehler für C ++ 14 mit ideone bestätigt: ideone.com/YRRT24
James Hirschorn
4

Warum kann ich a nicht an unique_ptreine Funktion übergeben?

Sie können, aber nicht durch Kopieren - weil std::unique_ptr<>nicht kopierkonstruierbar ist.

Dies ist sicherlich der Hauptzweck des Konstrukts?

Unter anderem std::unique_ptr<>wird entwickelt , um eindeutig markieren einzigartiges Eigentum (im Gegensatz zu std::shared_ptr<>).

Und am seltsamsten ist, warum ist dies eine gute Möglichkeit, es weiterzugeben?

Denn in diesem Fall gibt es keine Kopierkonstruktion.

Nielk
quelle
Danke für deine Antwort. Können Sie aus Interesse erklären, warum die letzte Art der Übergabe nicht den Kopierkonstruktor verwendet? Ich hätte gedacht, dass die Verwendung des Konstruktors von unique_ptr eine Instanz auf dem Stapel erzeugen würde, die mit dem Kopierkonstruktor in die Argumente für MyFunc () kopiert würde. Obwohl ich zugebe, dass meine Erinnerung in diesem Bereich etwas vage ist.
user3690202
2
Da es sich um einen r-Wert handelt, wird stattdessen der Verschiebungskonstruktor aufgerufen. Obwohl Ihr Compiler dies sicherlich trotzdem optimieren wird.
Nielk
0

Da unique_ptres sich um ein eindeutiges Eigentum handelt, versuchen Sie es, wenn Sie es als Argument übergeben möchten

MyFunc(move(ptr));

Aber danach wird der Zustand von ptrin mainsein nullptr.

0x6773
quelle
4
"wird undefiniert sein" - nein, es wird null sein oder unique_ptrwäre eher nutzlos.
TC
0

Die Übergabe std::unique_ptr<T>als Wert an eine Funktion funktioniert nicht, da sie, wie Sie bereits erwähnt haben, unique_ptrnicht kopierbar ist.

Was ist damit?

std::unique_ptr<T> getSomething()
{
   auto ptr = std::make_unique<T>();
   return ptr;
}

Dieser Code funktioniert

Riste
quelle
Wenn dieser Code funktioniert, würde ich vermuten, dass er nicht den unique_ptr kopiert, sondern tatsächlich die Verschiebungssemantik verwendet, um ihn zu verschieben.
user3690202
könnte die Return Value Optimization (RVO) sein, denke ich
Noueman Khalikine