unique_ptr Funktionsargument
-
Gibt es einen Unterschied zwischen
void f(std::unique_ptr<int> u);und
void f(std::unique_ptr<int>&& u)?
Welches sollte man verwenden?
-
Welches sollte man verwenden?
Hängt davon ab, wofür du das brauchst. Wobei ich ernsthaft sagen muss, wenn du einfach deine mit
newallozierten Objekte RAII-konform und exception-safe verwalten willst, nimmstd::shared_ptrfür Funktionsparameter, undunique_ptrfür bspw. Klassenmember.Gibt es einen Unterschied zwischen [...]
Nicht viel.
Sehen wir uns mal die möglichen Konstruktoren an, die der Variante 1 verschiedene Aufrufmöglichkeiten geben*:
constexpr unique_ptr (nullptr_t) noexcept : unique_ptr() {} unique_ptr (pointer p, typename conditional<is_reference<D>::value,D,const D&> del) noexcept; unique_ptr (pointer p, typename remove_reference<D>::type&& del) noexcept; unique_ptr (unique_ptr&& x) noexcept; template <class U, class E> unique_ptr (unique_ptr<U,E>&& x) noexcept; template <class U> unique_ptr (auto_ptr<U>&& x) noexcept;Beispiel (wir nehmen an, nur das erste ist deklariert, das führt sonst zu Ambiguitäten):
f( {new A, {}} ); // (1) f( std::unique_ptr<A>{new A} ); // (2) f( nullptr ); // (3) std::unique_ptr<A> bla{ new A }; // (4) f( std::move(bla) ); // (5)Aber: Was meinte ich oben mit den Ambiguitäten? Es ist nämlich nun einmal so, dass alle Statements oben auch mit der zweiten Funktionsdeklaration kompilieren! Denn rvalue-Referenzen können genau wie lvalue-to-const-Referenzen initialisiert werden; Es wird dann eine Temporary an sie gebunden (wie auch in Bsp. (2)), das funktioniert immer, wenn der Ausdruck mit dem initialisiert wird zum Zieltyp konvertiert werden kann.
Es gelten, solange andere Möglichkeiten ausfallen, sogar die gleichen semantischen Regeln wie bei (non-reference) copy-initialization (siehe §8.5.3/5)! Das heißt, von der Semantik beim Funktionsaufruf sollten beide Varianten gleich sein.Ob sie sich semantisch komplett gleich verhalten: Nein. Die Typen der Funktionsparameter sind immer noch verschieden (schon rein technisch gesehen). Aber:
named rvalue references are treated as lvalues
Das heißt, folgender Code
void f( std::unique_ptr<A>&& ptr ) { std::cout << "Erste\n"; } void f( std::unique_ptr<A>& ptr ) { std::cout << "Zweite\n"; } int main() { std::unique_ptr<A>&& bla{ new A }; f( bla ); }Würde (auch bei f() statt main() mit bla als Funktionsparameter) bei beiden Parametertypen Zweite ausgeben. Beides in dem Kontext lvalues.
*: Also prinzipiell alle nicht expliziten oder als
deleteddefinierten Konstruktoren.
-
Irgendwie hast du mich an der Stelle abgehängt, an der ich der Meinung war, dass in der ersten Deklaration der (nicht vorhandene) Kopierkonstruktor benötigt wird und somit der Unterschied ist, dass die erste Variante schlicht nicht funktioniert

-
Bashar schrieb:
Irgendwie hast du mich an der Stelle abgehängt, an der ich der Meinung war, dass in der ersten Deklaration der (nicht vorhandene) Kopierkonstruktor benötigt wird und somit der Unterschied ist, dass die erste Variante schlicht nicht funktioniert

copy-list-initialization, Bashar!
if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7).
struct B { B(B const&) = delete; B( B&& ) { std::cout << "B(B&&)\n"; } B( int, char ) { std::cout << "B(int, char)\n"; } }; int main() { B b = {4, '3'}; }Wo wird hier irgendein B-Objekt kopiert?
-
Oh.
-
Sone schrieb:
Welches sollte man verwenden?
Hängt davon ab, wofür du das brauchst. Wobei ich ernsthaft sagen muss, wenn du einfach deine mit
newallozierten Objekte RAII-konform und exception-safe verwalten willst, nimmstd::shared_ptrfür Funktionsparameter, undunique_ptrfür bspw. Klassenmember.Blödsinn. Warum sollte man das tun?
-
Sone trifft da implizite Annahmen über die use-cases. Zum Beispiel will man nicht, das ein Zeiger auf ein Objekt kopiert wird, obwohl das Objekt hätte kopiert werden sollen. Aber ich halte die use-case annahmen nicht für gerechtfertigt.
-
TyRoXx schrieb:
Sone schrieb:
Welches sollte man verwenden?
Hängt davon ab, wofür du das brauchst. Wobei ich ernsthaft sagen muss, wenn du einfach deine mit
newallozierten Objekte RAII-konform und exception-safe verwalten willst, nimmstd::shared_ptrfür Funktionsparameter, undunique_ptrfür bspw. Klassenmember.Blödsinn. Warum sollte man das tun?
shared_ptrist handy :p . Du kannst einen shared_ptr so übergeben (er ist kopierbar und regelt das definiert). Du hastmake_shared- exception-safe.Natürlich sollte man aber trotzdem standardmäßig
unique_ptrverwenden, da hast du völlig Recht, tut mir Leid.shared_ptrimpliziert, dass die Besitzverhältnisse unklar sind.Mir scheint, für
unique_ptrgibt es da nur zwei sinnvolle Fälle: Du movest einen alten unique_ptr (Besitz wechselt), oder du erstellst direkt einen neuen (Besitz hat direkt die aufzurufende Funktion). Wie oben gezeigt halt.Zum Beispiel will man nicht, das ein Zeiger auf ein Objekt kopiert wird, obwohl das Objekt hätte kopiert werden sollen.
Das ist was anderes. Wieso überhaupt einen **Smart-**Pointer als Funktionsparameter, wenn diese den Besitz nicht in irgendeiner Weise übernimmt?
Wenn du eine Kopie möchtest, kannst du auch eine Referenz auf ein Objekt im Funktionsparameter nehmen und intern (falls du die Kopie auf dem Heap brauchst) einen Smart-Pointer, natürlich standardmäßig einenunique_ptr, erstellen.
-
unique_ptr übergeben schrieb:
Gibt es einen Unterschied zwischen
void f(std::unique_ptr<int> u);und
void f(std::unique_ptr<int>&& u)?
Du hast im ersten Fall eine Übergabe "by value". Das unique_ptr-Objekt lebt da funktionslokal im automatischen Speicher solange bis der Funktionsaufruf abgeschlossen ist.
Im zweiten Fall ist es eine Übergabe "by reference". Da hat die Funktion nur eine Referenz auf ein unique_ptr-Objekt, was wer weiß wo und wie lange lebt. Typischerweise verweisen Rvalue-Referenzen auf kurzlebige Objekte. Muss aber nicht.
unique_ptr übergeben schrieb:
Welches sollte man verwenden?
Mir fällt auf Anhieb kein Anwendungsfall ein, wo es einen deutlichen Unterschied machen würde. Ich könnte mir aber vorstellen, dass viele, die sich dann Deinen Code durchlesen würden, sich darüber wundern, warum du da eine Rvalue-Referenz verwendet hast. std::unique_ptr ist fast so billig "move-bar" wie ein roher Zeiger. Von daher würde ich mir die Indirektion (mit der Rvalue-Referenz) sparen.
Hast du ein praktisches Beispiel parat, wo Du dir unsicher bist, wie der Funktionsparameter am besten zu definieren ist?
-
Hängt davon ab, wofür du das brauchst. Wobei ich ernsthaft sagen muss, wenn du einfach deine mit new allozierten Objekte RAII-konform und exception-safe verwalten willst, nimm std::shared_ptr für Funktionsparameter, und unique_ptr für bspw. Klassenmember.
Bloedsinn. Wenn ich shared-ownership ausdruecken will, dann nehme ich shared_ptr, wenn ich unique-ownership ausdruecken will, dann nehme ich unique_ptr. Wenn ich keine ownership ausdruecken will, dann nehme ich einen rohen Zeiger.
shared_ptr ist Handy
Ein dummes Smily macht es nicht besser. Ein Hammer ist auch Handy, doch fuer Schrauben benutze ich dennoch ein anderes Werkzeug.
-
Sone schrieb:
Das ist was anderes. Wieso überhaupt einen **Smart-**Pointer als Funktionsparameter, wenn diese den Besitz nicht in irgendeiner Weise übernimmt?
Das war für deine in-klassen-unique-pointer Pauschalaussage, die du völlig ohne Begründung genannt hast. Da will ich dir einmal was gutes tun...naja. Das hab ich davon. Nevermore.
-
void f(std::unique_ptr<int> u);Geht das überhaupt? Ich dachte man kann von einem unique_ptr keine Kopie machen.
-
TNA schrieb:
void f(std::unique_ptr<int> u);Geht das überhaupt? Ich dachte man kann von einem unique_ptr keine Kopie machen.
Exakt meine Frage auf der ersten Seite, auch dort beantwortet.
-
Deshalb frage ich, aber ich habe nicht gesehen das jemand direkt darauf geantwortet hätte oder ich verstehe zumindest die Antwort nicht.
-
void f(std::unique_ptr<int>&& u)Eine RValue-Referenz als Funktionsparameter mache ich doch wenn ich Ownership übertragen möchte oder? Also der Aufrufer weiß an der Stelle, dass er das übergebene Objekt nicht weiter benutzen kann oder darf oder?
-
TNA schrieb:
Eine RValue-Referenz als Funktionsparameter mache ich doch wenn ich Ownership übertragen möchte oder?
Nein, sondern wenn nur RValues als Argumente akzeptiert werden sollen. Typischerweise sind das temporäre Objekte oder Rückgabewerte von
std::move(). Am ehesten brauchst du RValue-Referenz-Parameter bei Move-Konstruktor und Move-Zuweisungsoperator (oder wenn du spezifisch optimieren willst, um Moves zu vermeiden). Ansonsten würde ich Pass By Value grundsätzlich vorziehen, da es flexibler ist und in vielen Fällen Codeduplizierung (Überladung fürT&&undconst T&) vermeidet.Wichtig ist zu sehen, dass RValue-Referenz und Move-Semantik verschiedene Konzepte sind. Zwar hängen sie insofern zusammen, als Move-Semantik auf Klassenebene über RValue-Referenzen implementiert wird; doch viele Moves geschehen, ohne dass ein
&&in der Funktion oder im Aufruf auftritt.Die Signatur
void f(std::unique_ptr<int> u);drückt bereits aus, dass hier Besitz zur Funktion übertragen wird -- einfach durch die Semantik von
unique_ptr, der nur gemoved werden kann. Pass By Value impliziert hier also Besitzübertragung. Diese Signatur verwirrt wahrscheinlich auch weniger.
-
@otze: Das meintest du! In Ordnung, tut mir Leid. Ja, ich habe falsche Annahmen über die Anforderungen gestellt. Tatsächlich sollte ich solche Pauschalaussagen einfach unterlassen.

oder wenn du spezifisch optimieren willst, um Moves zu vermeiden
*Kopien
Hier wird auch die Falschheit meiner Aussage deutlich. Hätte man einen
shared_ptrverwendet, so könnte man bspw. auch einen anderenshared_ptrübergeben - es kann aber nie davon ausgegangen werden, dass der Ownership ganz an die Funktion übergeht. Beiunique_ptrist das, wie Nexus erklärt hat, impliziert.
-
Sone schrieb:
oder wenn du spezifisch optimieren willst, um Moves zu vermeiden
*Kopien
Nein, ich meinte schon Moves.
Mit RValue-Referenzen hast du bereits eine Referenz auf das Originalobjekt, daher ist der Move unnötig.
-
Nexus schrieb:
Sone schrieb:
oder wenn du spezifisch optimieren willst, um Moves zu vermeiden
*Kopien
Nein, ich meinte schon Moves.
Mit RValue-Referenzen hast du bereits eine Referenz auf das Originalobjekt, daher ist der Move unnötig.
Ach, du meinst mit Move statt dem Cast zu einer rvalue-Referenz durch
std::moveden Aufruf eines Move-Konstruktors. Klar, der kann natürlich auch Overhead verursachen.
-
Nexus schrieb:
Die Signatur
void f(std::unique_ptr<int> u);drückt bereits aus, dass hier Besitz zur Funktion übertragen wird -- einfach durch die Semantik von
unique_ptr, der nur gemoved werden kann. Pass By Value impliziert hier also Besitzübertragung. Diese Signatur verwirrt wahrscheinlich auch weniger.Gerade diese Signatur verwirrt mich extrem. Es war doch genau die Frage, wie man ein Objekt per Value über geben kann, dass nicht kopiert werden kann. Was für einen Sinn macht das?
-
TNA schrieb:
Es war doch genau die Frage, wie man ein Objekt per Value über geben kann, dass nicht kopiert werden kann. Was für einen Sinn macht das?
Das Verständnis "Pass By Value == Kopie" ist veraltet.
In C++11 kann Pass By Value den Aufruf eines Move-Konstruktors statt eines Kopierkonstruktors bedeuten. Daher macht es sehr wohl Sinn, nichtkopierbare Objekte als Wert zu übergeben.
Lies dir vielleicht die Artikelserie auf C++Next durch, da wird Move-Semantik gut erklärt.