make_unique und perfekte Weiterleitung


216

Warum gibt es std::make_uniquein der Standard-C ++ 11-Bibliothek keine Funktionsvorlage? ich finde

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

ein bisschen ausführlich. Wäre das Folgende nicht viel schöner?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

Dies verbirgt das newschön und erwähnt den Typ nur einmal.

Wie auch immer, hier ist mein Versuch einer Implementierung von make_unique:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Ich habe eine ganze Weile std::forwardgebraucht, um das Zeug zu kompilieren, aber ich bin mir nicht sicher, ob es richtig ist. Ist es? Was genau bedeutet std::forward<Args>(args)...das? Was macht der Compiler daraus?


1
Ich bin mir ziemlich sicher, dass wir diese Diskussion schon einmal geführt haben. Beachten Sie auch, dass unique_ptrein zweiter Vorlagenparameter erforderlich ist, den Sie irgendwie berücksichtigen sollten - das unterscheidet sich von shared_ptr.
Kerrek SB

5
@Kerrek: Ich glaube nicht , es sinnvoll zu parametrieren machen würde make_uniquemit einem benutzerdefinierten deleter, weil offensichtlich ordnet sie über einfache alten newund daher muss schlicht verwenden alt delete:)
fredoverflow

1
@Fred: Das stimmt. Der Vorschlag make_uniquewürde sich also auf die newZuweisung beschränken ... Nun, es ist in Ordnung, wenn Sie ihn schreiben möchten, aber ich kann sehen, warum so etwas nicht zum Standard gehört.
Kerrek SB

4
Eigentlich verwende ich gerne eine make_uniqueVorlage, da der Konstruktor von std::unique_ptrexplizit ist und es daher ausführlich ist, unique_ptrvon einer Funktion zurückzukehren. Außerdem würde ich lieber verwenden auto p = make_unique<foo>(bar, baz)als std::unique_ptr<foo> p(new foo(bar, baz)).
Alexandre C.

Antworten:


157

Herb Sutter, Vorsitzender des C ++ - Standardisierungsausschusses, schreibt in seinem Blog :

Dass C ++ 11 nicht enthalten make_uniqueist, ist teilweise ein Versehen und wird mit ziemlicher Sicherheit in Zukunft hinzugefügt.

Er gibt auch eine Implementierung an, die mit der vom OP angegebenen identisch ist.

Bearbeiten: std::make_unique Jetzt ist Teil von C ++ 14 .


2
Eine make_uniqueFunktionsvorlage selbst garantiert keine ausnahmesicheren Aufrufe. Es beruht auf der Konvention, dass der Anrufer sie verwendet. Im Gegensatz dazu basiert die strikte statische Typprüfung (die den Hauptunterschied zwischen C ++ und C darstellt) auf der Idee , die Sicherheit über Typen zu erzwingen . Und dafür make_uniquekann einfach eine Klasse statt Funktion sein. Siehe zum Beispiel meinen Blog-Artikel vom Mai 2010. Er ist auch mit der Diskussion auf Herbs Blog verknüpft.
Prost und hth. - Alf

1
@ DavidRodríguez-dribeas: Lesen Sie Herbs Blog, um das Problem der Ausnahmesicherheit zu verstehen. Vergessen Sie "nicht entworfen, um abgeleitet zu werden", das ist nur Müll. Anstelle meines Vorschlags, make_uniqueeine Klasse zu erstellen, denke ich jetzt, dass es besser ist, eine Funktion zu erstellen make_unique_t, die a erzeugt . Der Grund dafür ist ein Problem mit der perversesten Analyse :-).
Prost und hth. - Alf

1
@ Cheersandhth.-Alf: Vielleicht haben wir einen anderen Artikel gelesen, weil der, den ich gerade gelesen habe, klar besagt, dass er make_uniquedie starke Ausnahmegarantie bietet. Oder Sie mischen das Tool mit seiner Verwendung. In diesem Fall ist keine Funktion ausnahmesicher. Bedenken Sie void f( int *, int* ){}, bietet eindeutig die no throwGarantie, aber nach Ihrer Argumentation ist es nicht ausnahmsicher, da es missbraucht werden kann. Schlimmer noch, void f( int, int ) {}ist auch keine Ausnahme sicher typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))
!:

2
@ Cheersandhth.-Alf: fragte ich über die Ausnahme Fragen in make_unique wie oben umgesetzt und du mich von Sutter auf einen Artikel verweisen, der Artikel , dass mein Google-Fu mich , dass in Staaten darauf make_uniquebietet die starke Ausnahme Garantie , die über Ihre Aussage widerspricht. Wenn Sie einen anderen Artikel haben, bin ich daran interessiert, ihn zu lesen. Meine ursprüngliche Frage lautet also: Wie ist make_unique(wie oben definiert) keine Ausnahme sicher? (Nebenbemerkung: Ja, ich denke, das make_unique verbessert die Ausnahmesicherheit an Stellen, an denen sie angewendet werden kann.)
David Rodríguez - Dribeas

3
... auch ich verfolge keine weniger sicher zu bedienende make_uniqueFunktion. Zuerst sehe ich nicht, wie unsicher dieser ist, und ich sehe nicht, wie das Hinzufügen eines zusätzlichen Typs ihn sicherer machen würde . Was ich weiß ist, dass ich daran interessiert bin , die Probleme zu verstehen, die diese Implementierung haben kann - ich kann keine sehen - und wie eine alternative Implementierung sie lösen würde. Was sind die Konventionen , die make_uniqueauf abhängt? Wie würden Sie die Typprüfung verwenden, um die Sicherheit zu gewährleisten? Das sind die beiden Fragen, auf die ich gerne eine Antwort hätte.
David Rodríguez - Dribeas

78

Schön, aber Stephan T. Lavavej (besser bekannt als STL) hat eine bessere Lösung für make_unique, die für die Array-Version korrekt funktioniert.

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

Dies ist auf seinem Core C ++ 6-Video zu sehen .

Eine aktualisierte Version der STL-Version von make_unique ist jetzt als N3656 verfügbar . Diese Version wurde in den Entwurf von C ++ 14 übernommen.


make_unique wurde von Stephen T Lavalej für das nächste Standard-Update vorgeschlagen.
Tominator

Hier ist ein Like zu ihm, der darüber spricht, es hinzuzufügen. channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/…
Tominator

warum all die unnötigen Änderungen vornehmen xeo, es war in Ordnung so wie es war. Dieser Code war genau so, wie Stephen T. Lavalej es ausdrückte, und er arbeitet für Dinkumware, die die Standardbibliothek verwaltet. Sie haben diese Pinnwand bereits kommentiert, also sollten Sie es wissen.
Tominator

3
Die Implementierung für make_uniquesollte in einem Header stehen. Header sollten keinen Namespace importieren (siehe Punkt 59 in Sutter / Alexandrescus Buch "C ++ Coding Standards"). Durch die Änderungen von Xeo wird vermieden, dass schlechte Praktiken gefördert werden.
Bret Kuhns

Leider nicht von VC2010 unterstützt, auch VC2012 denke ich, die beide nicht varidic tempalte Parameter unterstützen
zhaorufei

19

std::make_sharedist nicht nur eine Abkürzung für std::shared_ptr<Type> ptr(new Type(...));. Es macht etwas, was man ohne es nicht machen kann.

Um seine Arbeit zu erledigen, std::shared_ptrmuss zusätzlich zum Speichern des Speichers für den eigentlichen Zeiger ein Verfolgungsblock zugewiesen werden. Da jedoch std::make_shareddas tatsächliche Objekt zugewiesen wird, ist es möglich, dass std::make_sharedsowohl das Objekt als auch der Verfolgungsblock in demselben Speicherblock zugewiesen werden .

Während std::shared_ptr<Type> ptr = new Type(...);also zwei Speicherzuordnungen (eine für die new, eine im std::shared_ptrVerfolgungsblock) vorhanden std::make_shared<Type>(...)wären , würde ein Speicherblock zugewiesen .

Das ist wichtig für viele potentielle Nutzer von std::shared_ptr. Das einzige, was ein std::make_uniquetun würde, ist etwas bequemer. Nichts weiter als das.


2
Es ist nicht erforderlich. Hinweis, aber nicht erforderlich.
Welpe

3
Dies dient nicht nur der Bequemlichkeit, sondern würde in bestimmten Fällen auch die Ausnahmesicherheit verbessern. Siehe dazu die Antwort von Kerrek SB.
Piotr99

19

Nichts hindert Sie daran, Ihren eigenen Helfer zu schreiben, aber ich glaube, dass der Hauptgrund für die Bereitstellung make_shared<T>in der Bibliothek darin besteht, dass tatsächlich ein anderer interner Typ eines gemeinsam genutzten Zeigers erstellt wird als der shared_ptr<T>(new T), der anders zugewiesen ist, und es gibt keine Möglichkeit, dies ohne den dedizierten zu erreichen Helfer.

Ihr make_uniqueWrapper hingegen ist nur syntaktischer Zucker um einen newAusdruck. Während er für das Auge angenehm aussieht, bringt er nichts newauf den Tisch. Korrektur: Dies ist in der Tat nicht der Fall: Ein Funktionsaufruf zum Umschließen des newAusdrucks bietet Ausnahmesicherheit, beispielsweise in dem Fall, in dem Sie eine Funktion aufrufen void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&). Wenn zwei newRaws in Bezug aufeinander nicht sequenziert sind, bedeutet dies, dass der andere Ressourcen möglicherweise ausläuft, wenn ein neuer Ausdruck mit einer Ausnahme fehlschlägt. Warum es make_uniqueim Standard kein gibt : Es wurde einfach vergessen. (Dies kommt gelegentlich vor. Es gibt auch kein globales Element std::cbeginim Standard, obwohl es eines geben sollte.)

Beachten Sie auch, dass unique_ptrein zweiter Vorlagenparameter erforderlich ist, den Sie irgendwie berücksichtigen sollten. Dies unterscheidet sich von shared_ptrder Verwendung der Typlöschung zum Speichern benutzerdefinierter Löscher, ohne sie zum Teil des Typs zu machen.


1
@FredOverflow: Der gemeinsam genutzte Zeiger ist eine relativ komplizierte Klasse. intern wird ein polymorpher Referenzsteuerblock beibehalten, es gibt jedoch verschiedene Arten von Steuerblöcken. shared_ptr<T>(new T)verwendet einen von ihnen, make_shared<T>()verwendet einen anderen. Das zuzulassen ist eine gute Sache, und die geteilte Version ist in gewisser Weise der leichteste gemeinsame Zeiger, den Sie bekommen können.
Kerrek SB

15
@FredOverflow: Weist shared_ptreinen Block dynamischen Speichers zu, um die Anzahl und die Aktion "Disposer" beim Erstellen von a aufrechtzuerhalten shared_ptr. Wenn Sie den Zeiger explizit übergeben, muss ein "neuer" Block erstellt werden. Wenn Sie ihn verwenden make_shared, können Sie Ihr Objekt und die Satellitendaten in einem einzigen Speicherblock (einem new) bündeln, was zu einer schnelleren Zuweisung / Freigabe, weniger Fragmentierung und (normalerweise) führt ) besseres Cache-Verhalten.
Matthieu M.

2
Ich denke, ich muss shared_ptr und Freunde erneut ansehen ...
Fredoverflow

4
-1 "Ihr make_unique-Wrapper hingegen ist nur syntaktischer Zucker um einen neuen Ausdruck. Obwohl er für das Auge angenehm aussieht, bringt er nichts Neues auf den Tisch." ist falsch. Es besteht die Möglichkeit eines ausnahmesicheren Funktionsaufrufs. Es gibt jedoch keine Garantie; Dafür müsste es eine Klasse sein, damit die formalen Argumente dieser Klasse deklariert werden können (Herb beschrieb dies als den Unterschied zwischen Opt-In und Opt-Out).
Prost und hth. - Alf

1
@ Cheersandhth.-Alf: Ja, das stimmt. Ich habe das seitdem realisiert. Ich werde die Antwort bearbeiten.
Kerrek SB

13

In C ++ wird 11 ...(im Vorlagencode) auch für die "Pack-Erweiterung" verwendet.

Voraussetzung ist, dass Sie es als Suffix eines Ausdrucks verwenden, der ein nicht erweitertes Paket von Parametern enthält, und dass der Ausdruck einfach auf jedes der Elemente des Pakets angewendet wird.

Bauen Sie zum Beispiel auf Ihrem Beispiel auf:

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

Letzteres ist falsch, denke ich.

Außerdem kann ein Argumentpaket nicht an eine nicht erweiterte Funktion übergeben werden. Ich bin mir über ein Paket von Vorlagenparametern nicht sicher.


1
Schön zu sehen, dass dies klargestellt ist. Warum nicht auch den variadic template Parameter einschließen?
Kerrek SB

@ Kerrek: weil ich mir über die seltsame Syntax nicht so sicher bin und nicht weiß, ob viele Leute damit gespielt haben. Also werde ich mich an das halten, was ich weiß. Es kann einen c ++ - FAQ-Eintrag rechtfertigen, wenn jemand motiviert und kompetent genug war, da die variadische Syntax ziemlich vollständig ist.
Matthieu M.

Die Syntax lautet std::forward<Args>(args)..., erweitert auf forward<T1>(x1), forward<T2>(x2), ....
Kerrek SB

1
: Ich denke forwardeigentlich erfordert immer einen Template-Parameter, nicht wahr?
Kerrek SB

1
@ Howard: richtig! Das Erzwingen der Instanziierung löst die Warnung aus ( ideone.com/GDNHb )
Matthieu M.

5

Inspiriert von der Implementierung von Stephan T. Lavavej fand ich es vielleicht schön, ein make_unique zu haben, das Array-Extents unterstützt. Es ist auf Github und ich würde gerne Kommentare dazu bekommen. Damit können Sie Folgendes tun:

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.