Po co używać std :: make_unique w C ++ 17?

96

O ile rozumiem, wprowadzono C ++ 14 std::make_unique, ponieważ w wyniku nieokreślenia kolejności oceny parametrów było to niebezpieczne:

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

(Wyjaśnienie: jeśli ocena najpierw przydzieli pamięć dla surowego wskaźnika, a następnie wywoła g()i wyjątek zostanie zgłoszony przed std::unique_ptrkonstrukcją, wtedy nastąpi wyciek pamięci).

Dzwonienie std::make_uniquebyło sposobem na ograniczenie kolejności połączeń, dzięki czemu wszystko było bezpieczne:

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

Od tego czasu C ++ 17 wyjaśnił kolejność oceny, czyniąc również składnię A bezpieczną, więc oto moje pytanie: czy nadal istnieje powód, aby używać konstruktora std::make_uniqueover std::unique_ptrw C ++ 17? Czy możesz podać kilka przykładów?

Na razie jedyny powód, jaki mogę sobie wyobrazić, to to, że pozwala na wpisanie MyClasstylko raz (zakładając, że nie musisz polegać na polimorfizmie std::unique_ptr<Base>(new Derived(param))). Jednak wydaje się to dość słabym powodem, zwłaszcza gdy std::make_uniquenie pozwala na określenie deleter, podczas gdy std::unique_ptrkonstruktor to robi.

Żeby było jasne, nie opowiadam się za usunięciem std::make_uniquez biblioteki standardowej (zachowanie tego ma sens przynajmniej ze względu na wsteczną kompatybilność), ale raczej zastanawiam się, czy nadal istnieją sytuacje, w których zdecydowanie preferowane jeststd::unique_ptr

Wieczny
źródło
4
Wydaje się jednak, że to dość słaby powód -> Dlaczego jest to słaby powód? Skutecznie redukuje powielanie kodu typu. Jeśli chodzi o usuwanie, jak często używasz niestandardowego narzędzia do usuwania std::unique_ptr? To nie jest argument przeciwkomake_unique
llllllllll
2
Mówię, że to słaby powód, bo gdyby nie było std::make_unique, myślę, że nie byłby to wystarczający powód, aby dodać go do STL, zwłaszcza gdy jest to składnia, która jest mniej wyrazista niż użycie konstruktora, nie więcej
Wieczny
1
Jeśli masz program utworzony w c ++ 14 przy użyciu make_unique, nie chcesz, aby funkcja została usunięta z stl. Lub jeśli chcesz, aby był kompatybilny wstecz.
Serge
2
@Serge To dobra uwaga, ale to trochę poza przedmiotem mojego pytania. Zrobię edycję, aby było jaśniej
Wieczny
1
@Eternal, przestań odnosić się do biblioteki standardowej C ++ jako STL, ponieważ jest ona niepoprawna i powoduje zamieszanie. Zobacz stackoverflow.com/questions/5205491/…
Marandil,

Odpowiedzi:

75

Masz rację, że główny powód został usunięty. Nadal istnieją nowe wytyczne dotyczące nieużywania nowych i mniej powodów związanych z pisaniem (nie trzeba powtarzać czcionki ani używać słowa new). Co prawda nie są to mocne argumenty, ale bardzo lubię nie widzieć neww moim kodzie.

Nie zapomnij też o konsystencji. Koniecznie powinieneś używać, make_sharedwięc używanie make_uniquejest naturalne i pasuje do wzoru. Zmiana std::make_unique<MyClass>(param)na std::make_shared<MyClass>(param)(lub odwrotnie) jest wtedy trywialna, gdzie składnia A wymaga znacznie więcej przepisania.

NathanOliver
źródło
41
@reggaeguitar Jeśli widzę new, muszę się zatrzymać i pomyśleć: jak długo ten wskaźnik będzie żył? Czy poradziłem sobie z tym poprawnie? Jeśli jest wyjątek, czy wszystko zostało poprawnie wyczyszczone? Chciałbym nie zadawać sobie tych pytań i tracić na to czasu, a jeśli nie używam new, nie muszę zadawać tych pytań.
NathanOliver,
5
Wyobraź sobie, że wykonujesz grep na wszystkich plikach źródłowych swojego projektu i nie znajdujesz ani jednego new. Czy to nie byłoby wspaniałe?
Sebastian Mach,
5
Główną zaletą wskazówki „nie używaj nowych” jest to, że jest prosta, więc jest to łatwa wskazówka dla mniej doświadczonych programistów, z którymi możesz pracować. Na początku nie zdawałem sobie z tego sprawy, ale to ma wartość samą w sobie
wieczny
@NathanOliver Właściwie nie musisz tego absolutnie używać std::make_shared- wyobraź sobie przypadek, w którym przydzielony obiekt jest duży i jest na niego wiele std::weak_ptrwskazujących: lepiej byłoby pozwolić, aby obiekt został usunięty zaraz po ostatnim udostępnieniu wskaźnik jest zniszczony i ma tylko mały wspólny obszar.
Dev Null
1
@NathanOliver nie. To, o czym mówię, to wada std::make_shared stackoverflow.com/a/20895705/8414561, gdzie pamięć użyta do przechowywania obiektu nie może zostać zwolniona, dopóki ostatnia std::weak_ptrnie zniknie (nawet jeśli wszystkie std::shared_ptrwskazują na to (i w konsekwencji sam obiekt) zostały już zniszczone).
Dev Null
52

make_uniqueodróżnia Tod T[]i T[N], unique_ptr(new ...)nie.

Możesz łatwo uzyskać niezdefiniowane zachowanie, przekazując wskaźnik, który został new[]ed do a unique_ptr<T>, lub przekazując wskaźnik, który został newed do a unique_ptr<T[]>.

Caleth
źródło
Jest gorzej: nie tylko nie, ale wręcz niemożliwy.
Deduplicator
22

Powodem jest krótszy kod bez duplikatów. Porównać

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

Można zaoszczędzić MyClass, newi szelki. To kosztuje tylko jeden znak więcej w marki w porównaniu z PTR .

SM
źródło
2
Cóż, jak powiedziałem w pytaniu, widzę, że wpisuje się mniej, mając tylko jedną wzmiankę o MyClass, ale zastanawiałem się, czy istnieje silniejszy powód, aby go używać
Eternal
2
W wielu przypadkach przewodnik po odliczeniach pomógłby wyeliminować <MyClass>część z pierwszego wariantu.
AnT
9
Zostało to już powiedziane w komentarzach do innych odpowiedzi, ale podczas gdy c ++ 17 wprowadził dedukcję typu szablonu dla konstruktorów, w przypadku, std::unique_ptrgdy jest to niedozwolone. Ma to związek z rozróżnianiem std::unique_ptr<T>istd::unique_ptr<T[]>
Wiecznym
19

Każde użycie newmusi być bardzo dokładnie sprawdzone pod kątem poprawności przez cały okres użytkowania; czy zostanie usunięty? Tylko raz?

Każde użycie make_uniquenie dla tych dodatkowych cech; tak długo, jak obiekt będący właścicielem ma „prawidłowy” czas życia, rekurencyjnie powoduje, że unikalny wskaźnik ma „poprawny”.

Otóż, prawdą jest, że unique_ptr<Foo>(new Foo())jest identyczny pod każdym względem od 1 do make_unique<Foo>(); wymaga tylko prostszego "grep twojego kodu źródłowego do wszystkich zastosowań w newcelu ich audytu".


1 właściwie kłamstwo w ogólnym przypadku. Idealne przekazywanie nie jest idealne, {}domyślny init, tablice są wyjątkami.

Yakk - Adam Nevraumont
źródło
Technicznie unique_ptr<Foo>(new Foo)nie jest do końca identyczny z make_unique<Foo>()... to drugie tak. new Foo()Ale poza tym tak.
Barry
@barry prawda, przeciążony operator new jest możliwy.
Yakk - Adam Nevraumont,
@dedup co to za wstrętne czary w C ++ 17?
Yakk - Adam Nevraumont,
2
@Deduplicator, podczas gdy c ++ 17 wprowadził dedukcję typu szablonu dla konstruktorów, w przypadku std::unique_ptrgdy jest to niedozwolone. Ma to związek z rozróżnianiem std::unique_ptr<T>istd::unique_ptr<T[]>
Wiecznym
@ Yakk-AdamNevraumont Nie miałem na myśli przeładowania nowego, po prostu miałem na myśli default-init vs value-init.
Barry,
0

Od tego czasu C ++ 17 wyjaśnił kolejność oceny, czyniąc również składnię A bezpieczną

To naprawdę nie wystarczy. Poleganie na niedawno wprowadzonej klauzuli technicznej jako gwarancji bezpieczeństwa nie jest bardzo solidną praktyką:

  • Ktoś mógłby skompilować ten kod w C ++ 14.
  • Zachęcałbyś do używania surowego w newinnym miejscu, np. Kopiując i wklejając swój przykład.
  • Jak sugeruje SM, ponieważ występuje duplikacja kodu, jeden typ może zostać zmieniony bez zmiany drugiego.
  • Jakiś rodzaj automatycznej refaktoryzacji IDE może przenieść to newgdzie indziej (ok, prawda, nie ma na to dużej szansy).

Ogólnie rzecz biorąc, dobrym pomysłem jest, aby kod był odpowiedni / niezawodny / wyraźnie poprawny bez uciekania się do warstw językowych, wyszukiwania pomniejszych lub niejasnych klauzul technicznych w standardzie.

(jest to zasadniczo ten sam argument, który przedstawiłem tutaj na temat kolejności niszczenia krotek).

einpoklum
źródło
-1

Rozważ void function (std :: unique_ptr (new A ()), std :: unique_ptr (new B ())) {...}

Załóżmy, że nowa A () się powiedzie, ale nowa B () zgłasza wyjątek: łapiesz ją, aby wznowić normalne wykonywanie programu. Niestety standard C ++ nie wymaga zniszczenia obiektu A i zwolnienia jego pamięci: pamięć po cichu przecieka i nie ma możliwości jej wyczyszczenia. Pakując A i B w std :: make_uniques, masz pewność, że wyciek nie wystąpi:

void function (std :: make_unique (), std :: make_unique ()) {...} Chodzi o to, że std :: make_unique i std :: make_unique są teraz obiektami tymczasowymi, a czyszczenie obiektów tymczasowych jest poprawnie określone w standard C ++: ich destruktory zostaną wyzwolone, a pamięć zwolniona. Więc jeśli możesz, zawsze wolisz przydzielać obiekty za pomocą std :: make_unique i std :: make_shared.

Dhanya Gopinath
źródło
4
Autor wyraźnie sprecyzował w pytaniu, że w C ++ 17 przecieki już się nie pojawią. „Od tego czasu C ++ 17 wyjaśnił kolejność oceny, czyniąc również składnię A bezpieczną, więc oto moje pytanie: (…)”. Nie odpowiedziałeś na jego pytanie.
R2RT