Warum std :: make_unique in C ++ 17 verwenden?

96

Soweit ich weiß, wurde C ++ 14 eingeführt std::make_unique, da dies aufgrund der nicht angegebenen Reihenfolge der Parameterauswertung unsicher war:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

(Erläuterung: Wenn die Auswertung zuerst den Speicher für den Rohzeiger zuweist, dann aufruft g()und vor der std::unique_ptrKonstruktion eine Ausnahme ausgelöst wird , geht der Speicher verloren.)

Berufung std::make_uniquewar ein Weg , um den Anruf zu beschränken, damit die Dinge sicher:

f(std::make_unique<MyClass>(param), g());             // Syntax B

Seitdem hat C ++ 17 die Auswertungsreihenfolge geklärt und Syntax A auch sicher gemacht. Hier ist meine Frage: Gibt es noch einen Grund, den Konstruktor von std::make_uniqueover std::unique_ptrin C ++ 17 zu verwenden? Können Sie einige Beispiele nennen?

Der einzige Grund, den ich mir vorstellen kann, ist, dass MyClassnur einmal getippt werden kann (vorausgesetzt, Sie müssen sich nicht auf Polymorphismus verlassen std::unique_ptr<Base>(new Derived(param))). Dies scheint jedoch ein ziemlich schwacher Grund zu sein, insbesondere wenn std::make_uniquees nicht erlaubt ist, einen Deleter anzugeben, während dies std::unique_ptrder Konstruktor tut.

Und um ganz klar zu sein, ich befürworte nicht das Entfernen std::make_uniqueaus der Standardbibliothek (es ist zumindest aus Gründen der Abwärtskompatibilität sinnvoll, dies beizubehalten), sondern frage mich, ob es noch Situationen gibt, in denen dies stark bevorzugt wirdstd::unique_ptr

Ewig
quelle
4
Dies scheint jedoch ein ziemlich schwacher Grund zu sein -> Warum ist es ein schwacher Grund? Es reduziert effektiv die Codeduplizierung des Typs. Wie oft verwenden Sie beim Löschen einen benutzerdefinierten Löscher std::unique_ptr? Es ist kein Argument gegenmake_unique
llllllllll
2
Ich sage, es ist ein schwacher Grund, denn wenn es std::make_uniqueüberhaupt keinen gäbe, denke ich nicht, dass dies Grund genug wäre, ihn der STL hinzuzufügen, insbesondere wenn es sich um eine Syntax handelt, die weniger aussagekräftig ist als die Verwendung des Konstruktors, nicht mehr
Ewiger
1
Wenn Sie ein Programm haben, das in c ++ 14 mit make_unique erstellt wurde, möchten Sie nicht, dass die Funktion aus stl entfernt wird. Oder wenn Sie möchten, dass es abwärtskompatibel ist.
Serge
2
@Serge Das ist ein guter Punkt, aber es ist ein bisschen neben dem Gegenstand meiner Frage. Ich werde eine Änderung vornehmen, um es klarer zu machen
Ewiger
1
@Eternal Bitte hören Sie auf, C ++ Standard Library als STL zu bezeichnen, da dies falsch ist und Verwirrung stiftet. Siehe stackoverflow.com/questions/5205491/…
Marandil

Antworten:

73

Sie haben Recht, dass der Hauptgrund entfernt wurde. Es gibt immer noch keine neuen Richtlinien und es gibt weniger Gründe für die Eingabe (Sie müssen den Typ nicht wiederholen oder das Wort verwenden new). Zugegeben, das sind keine starken Argumente, aber ich mag es wirklich, nicht newin meinem Code zu sehen.

Vergessen Sie auch nicht die Konsistenz. Sie sollten es unbedingt verwenden, make_shareddamit make_uniquees natürlich ist und zum Muster passt. Es ist dann trivial, std::make_unique<MyClass>(param)zu std::make_shared<MyClass>(param)(oder umgekehrt) zu wechseln , wo die Syntax A viel mehr Umschreiben erfordert.

NathanOliver
quelle
40
@reggaeguitar Wenn ich ein newsehe, muss ich innehalten und überlegen : Wie lange wird dieser Zeiger noch leben? Habe ich richtig damit umgegangen? Wenn es eine Ausnahme gibt, wird alles korrekt bereinigt? Ich möchte mir diese Fragen nicht stellen und meine Zeit damit verschwenden, und wenn ich sie nicht benutze new, muss ich diese Fragen nicht stellen.
NathanOliver
5
Stellen Sie sich vor, Sie durchsuchen alle Quelldateien Ihres Projekts und finden keine einzige new. Wäre das nicht wunderbar?
Sebastian Mach
5
Der Hauptvorteil der Richtlinie "Nicht neu verwenden" besteht darin, dass sie einfach ist. Daher ist sie eine einfache Richtlinie, die Sie den weniger erfahrenen Entwicklern geben können, mit denen Sie möglicherweise arbeiten. Ich hatte es zuerst nicht
Ewiger
@NathanOliver Sie müssen es eigentlich nicht unbedingt verwenden std::make_shared- stellen Sie sich einen Fall vor, in dem das zugewiesene Objekt groß ist und viele std::weak_ptr-s darauf hinweisen: Es ist besser, das Objekt löschen zu lassen, sobald das letzte freigegeben wurde Zeiger ist zerstört und leben mit nur einem kleinen gemeinsamen Bereich.
Dev Null
1
@ NathanOliver würdest du nicht. Ich spreche von dem Nachteil von std::make_shared stackoverflow.com/a/20895705/8414561, bei dem der Speicher, in dem das Objekt gespeichert wurde, erst freigegeben werden kann, wenn der letzte Speicher nicht mehr vorhanden std::weak_ptrist (auch wenn alle std::shared_ptr-s darauf zeigen (und) folglich wurde das Objekt selbst) bereits zerstört).
Dev Null
50

make_uniqueunterscheidet sich Tvon T[]und T[N], unique_ptr(new ...)nicht.

Sie können leicht undefiniertes Verhalten erhalten, indem Sie einen Zeiger new[]an a übergeben unique_ptr<T>oder einen Zeiger newan a übergeben unique_ptr<T[]>.

Caleth
quelle
Es ist schlimmer: Es ist nicht nur nicht, es ist absolut unfähig dazu.
Deduplikator
21

Der Grund ist, kürzeren Code ohne Duplikate zu haben. Vergleichen Sie

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

Sie sparen MyClass, newund Klammern. Es kostet nur ein Zeichen mehr in make im Vergleich zu ptr .

SM
quelle
2
Nun, wie ich in der Frage sagte, kann ich sehen, dass es mit nur einer Erwähnung weniger tippt MyClass, aber ich habe mich gefragt, ob es einen stärkeren Grund gibt, es zu verwenden
Ewiger
2
In vielen Fällen würde der Abzugsleitfaden dazu beitragen, das <MyClass>Teil in der ersten Variante zu eliminieren .
Am
9
Es wurde bereits in den Kommentaren für andere Antworten gesagt, aber während c ++ 17 den Abzug des Vorlagentyps für Konstruktoren einführte, ist dies im Fall std::unique_ptrnicht zulässig. Es hat mit Unterscheidung std::unique_ptr<T>undstd::unique_ptr<T[]>
Ewigen
19

Jede Verwendung von newmuss besonders sorgfältig auf lebenslange Korrektheit geprüft werden. wird es gelöscht? Nur einmal?

Jede Verwendung von make_uniquenicht für diese zusätzlichen Eigenschaften; Solange das besitzende Objekt eine "korrekte" Lebensdauer hat, wird der eindeutige Zeiger rekursiv "korrekt".

Nun ist es wahr, dass unique_ptr<Foo>(new Foo())in jeder Hinsicht 1 bis identisch ist make_unique<Foo>(); Es erfordert lediglich eine einfachere "Grep Ihren Quellcode für alle Verwendungszwecke, um sie newzu prüfen".


Ich bin eigentlich eine Lüge im allgemeinen Fall. Perfekte Weiterleitung ist nicht perfekt, {}Standard-Init, Arrays sind alle Ausnahmen.

Yakk - Adam Nevraumont
quelle
Technisch unique_ptr<Foo>(new Foo)ist das nicht ganz identisch mit make_unique<Foo>()... Letzteres tut es new Foo()aber sonst ja.
Barry
@barry true, überladener Operator neu ist möglich.
Yakk - Adam Nevraumont
@dedup was für eine üble C ++ 17-Hexerei ist das?
Yakk - Adam Nevraumont
2
@Deduplicator, während c ++ 17 den Abzug des Vorlagentyps für Konstruktoren einführte, falls std::unique_ptrdieser nicht zulässig ist. Wenn es um die Unterscheidung geht std::unique_ptr<T>undstd::unique_ptr<T[]>
Ewiger
@ Yakk-AdamNevraumont Ich meinte nicht, neue zu überladen, ich meinte nur default-init vs value-init.
Barry
0

Seitdem hat C ++ 17 die Auswertungsreihenfolge geklärt und Syntax A ebenfalls sicher gemacht

Das ist wirklich nicht gut genug. Es ist keine sehr robuste Praxis, sich auf eine kürzlich eingeführte technische Klausel als Sicherheitsgarantie zu stützen:

  • Jemand könnte diesen Code in C ++ 14 kompilieren.
  • Sie würden die Verwendung von Rohdaten an newanderer Stelle fördern , z. B. indem Sie Ihr Beispiel kopieren.
  • Wie SM vorschlägt, kann ein Typ geändert werden, ohne dass der andere geändert wird, da Code dupliziert wird.
  • Eine Art automatisches IDE-Refactoring könnte dies an eine newandere Stelle verschieben (ok, zugegeben, keine große Chance dafür).

Im Allgemeinen ist es eine gute Idee, dass Ihr Code angemessen / robust / eindeutig gültig ist, ohne auf Sprachkenntnisse zurückzugreifen und kleinere oder undurchsichtige technische Klauseln im Standard nachzuschlagen.

(Dies ist im Wesentlichen das gleiche Argument, das ich hier über die Reihenfolge der Tupelzerstörung vorgebracht habe.)

einpoklum
quelle
-1

Betrachten Sie die void-Funktion (std :: unique_ptr (neues A ()), std :: unique_ptr (neues B ())) {...}

Angenommen, neues A () ist erfolgreich, aber neues B () löst eine Ausnahme aus: Sie fangen es ab, um die normale Ausführung Ihres Programms fortzusetzen. Leider verlangt der C ++ - Standard nicht, dass Objekt A zerstört und sein Speicher freigegeben wird: Der Speicher leckt stillschweigend und es gibt keine Möglichkeit, ihn zu bereinigen. Wenn Sie A und B in std :: make_uniques einschließen, können Sie sicher sein, dass das Leck nicht auftritt:

void function (std :: make_unique (), std :: make_unique ()) {...} Hier geht es darum, dass std :: make_unique und std :: make_unique jetzt temporäre Objekte sind und die Bereinigung temporärer Objekte in korrekt angegeben ist der C ++ - Standard: Ihre Destruktoren werden ausgelöst und der Speicher freigegeben. Wenn Sie können, ziehen Sie es immer vor, Objekte mit std :: make_unique und std :: make_shared zuzuweisen.

Dhanya Gopinath
quelle
4
Der Autor hat ausdrücklich in Frage gestellt, dass in C ++ 17 keine Lecks mehr auftreten dürfen. "Seitdem hat C ++ 17 die Auswertungsreihenfolge geklärt und Syntax A auch sicher gemacht. Hier ist meine Frage: (...)". Sie haben seine Frage nicht beantwortet.
R2RT