make_unique i idealne przekazywanie

216

Dlaczego std::make_uniquew standardowej bibliotece C ++ 11 nie ma szablonu funkcji? znajduję

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

trochę gadatliwy. Czy poniższe rzeczy nie byłyby o wiele ładniejsze?

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

To newładnie ukrywa i wspomina o typie tylko raz.

Tak czy inaczej, oto moja próba wdrożenia 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)...));
}

Sporo czasu zajęło mi std::forwardskompilowanie tego pliku , ale nie jestem pewien, czy jest poprawny. Czy to jest Co dokładnie std::forward<Args>(args)...znaczy? Co robi z tego kompilator?

fredoverflow
źródło
1
Jestem całkiem pewien, że rozmawialiśmy już wcześniej ... zauważ także, że unique_ptrbierze drugi parametr szablonu, na który powinieneś jakoś zezwolić - który jest inny niż shared_ptr.
Kerrek SB,
5
@Kerrek: Nie wydaje mi się, że warto sparametryzować make_uniqueza pomocą niestandardowego programu usuwającego, ponieważ oczywiście alokuje on za pomocą zwykłego starego newi dlatego należy używać zwykłego starego delete:)
Fredoverflow
1
@Fred: To prawda. Tak więc proponowany make_uniquebyłby ograniczony do newalokacji ... no cóż, dobrze jest, jeśli chcesz to napisać, ale rozumiem, dlaczego coś takiego nie jest częścią standardu.
Kerrek SB
4
W rzeczywistości lubię używać make_uniqueszablonu, ponieważ konstruktor std::unique_ptrjest jawny, a zatem zwracanie się unique_ptrz funkcji jest pełne . Wolałbym też użyć auto p = make_unique<foo>(bar, baz)niż std::unique_ptr<foo> p(new foo(bar, baz)).
Alexandre C.,
5
make_uniquekapie C++14, zobacz isocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meeting
stefan

Odpowiedzi:

157

Herb Sutter, przewodniczący komitetu normalizacyjnego C ++, pisze na swoim blogu :

To, że C ++ 11 nie obejmuje, make_uniquejest częściowo niedopatrzeniem i prawie na pewno zostanie dodane w przyszłości.

Podaje także implementację identyczną z tą podaną przez PO.

Edycja: std::make_unique teraz jest częścią C ++ 14 .

Johan Råde
źródło
2
make_uniqueSzablon funkcja sama w sobie nie gwarantuje bezpieczne połączenia wyjątków. Opiera się na konwencji, że osoba dzwoniąca z niej korzysta. Natomiast ścisłe sprawdzanie typów statycznych (które jest główną różnicą między C ++ i C) opiera się na zasadzie wymuszania bezpieczeństwa poprzez typy. I do tego make_uniquemoże być po prostu klasą zamiast funkcji. Na przykład zobacz mój artykuł na blogu z maja 2010 r. Jest również powiązany z dyskusją na blogu Herb.
Pozdrawiam i hth. - Alf
1
@ DavidRodríguez-dribeas: przeczytaj blog Herb, aby zrozumieć wyjątek dotyczący bezpieczeństwa. zapomnij o „nieprzeznaczonym do uzyskania”, to tylko śmieci. zamiast mojej sugestii stworzenia make_uniqueklasy, myślę, że teraz lepiej jest uczynić ją funkcją, która generuje make_unique_t, przyczyną tego jest problem z najbardziej perwersyjną analizą :-).
Pozdrawiam i hth. - Alf
1
@ Cheersandhth.-Alf: Może przeczytaliśmy inny artykuł, ponieważ ten, który właśnie przeczytałem, wyraźnie stwierdza, że make_uniqueoferuje silną gwarancję wyjątku. A może miksujesz narzędzie z jego użyciem, w którym to przypadku żadna funkcja nie jest wyjątkowo bezpieczna. Zastanów się void f( int *, int* ){}, wyraźnie oferuje no throwgwarancję, ale według twojego rozumowania nie jest ona wyjątkowo bezpieczna, ponieważ może być nadużywana. Co gorsza, void f( int, int ) {}wyjątek też nie jest bezpieczny !: typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
David Rodríguez - dribeas
2
@ Cheersandhth.-Alf: Poprosiłem o kwestii wyjątków w make_unique wdrożone jak wyżej, a ty mi punkt artykule Sutter, artykuł, że moje google-fu wskazał mi do państw, które make_uniqueoferuje silny gwarancja wyjątek , który zaprzecza swoje oświadczenie powyżej. Jeśli masz inny artykuł, jestem zainteresowany jego przeczytaniem. Więc moje oryginalne pytanie brzmi: Jak make_unique(jak zdefiniowano powyżej) wyjątek nie jest bezpieczny? (Sidenote: tak, myślę, że make_unique poprawia to bezpieczeństwo wyjątkowe w miejscach, w których można je zastosować)
David Rodríguez - dribeas
3
... także nie dążę do mniej bezpiecznej make_uniquefunkcji. Po pierwsze, nie widzę, jak ten jest niebezpieczny i nie widzę, jak dodanie dodatkowego typu uczyniłoby go bezpieczniejszym . Wiem, że jestem zainteresowany zrozumieniem problemów, które może mieć ta implementacja - nie widzę żadnych - oraz tego, jak alternatywna implementacja mogłaby je rozwiązać. Jakie są konwencje , które make_uniquezależy? W jaki sposób zastosowałbyś sprawdzanie typu w celu wymuszenia bezpieczeństwa? To są dwa pytania, na które chciałbym odpowiedzieć.
David Rodríguez - dribeas
78

Fajnie, ale Stephan T. Lavavej (lepiej znany jako STL) ma lepsze rozwiązanie make_unique, które działa poprawnie dla wersji tablicowej.

#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)...);
}

Można to zobaczyć na jego filmie Core C ++ 6 .

Zaktualizowana wersja make_unique STL jest teraz dostępna jako N3656 . Ta wersja została przyjęta do wersji roboczej C ++ 14.

tominator
źródło
make_unique zaproponował Stephen T. Lavalej w następnej aktualizacji standardowej.
tominator
po co wprowadzać wszystkie niepotrzebne zmiany xeo, wszystko było w porządku. Ten kod był dokładnie taki, jak ujął to Stephen T. Lavalej i pracuje dla dinkumware, które utrzymuje bibliotekę std. Skomentowałeś już tę ścianę, więc powinieneś wiedzieć.
tominator
3
Implementacja dla make_uniquepowinna iść w nagłówku. Nagłówki nie powinny importować przestrzeni nazw (patrz pozycja # 59 w książce Sutter / Alexandrescu „C ++ Standardy kodowania”). Zmiany w Xeo pomagają uniknąć zachęcania do złych praktyk.
Bret Kuhns,
niestety nie obsługiwany przez VC2010, nawet VC2012 myślę, że oba nie obsługują varidic tempalte parametru
zhaorufei
19

std::make_sharednie jest tylko skrótem std::shared_ptr<Type> ptr(new Type(...));. Robi coś, bez czego nie można się obejść.

Aby wykonać swoje zadanie, std::shared_ptrmusi przydzielić blok śledzenia oprócz przechowywania pamięci dla rzeczywistego wskaźnika. Ponieważ jednak std::make_sharedalokuje rzeczywisty obiekt, możliwe jest, że std::make_sharedalokuje zarówno obiekt, jak i blok śledzenia w tym samym bloku pamięci.

Tak więc podczas gdy std::shared_ptr<Type> ptr = new Type(...);byłyby dwa przydziały pamięci (jeden dla new, jeden w std::shared_ptrbloku śledzenia), std::make_shared<Type>(...)przydzieliłby jeden blok pamięci.

Jest to ważne dla wielu potencjalnych użytkowników std::shared_ptr. Jedyne, co std::make_uniquemożna zrobić, to nieco wygodniej. Nic więcej.

Nicol Bolas
źródło
2
To nie jest wymagane. Podpowiedziany, ale nie wymagany.
Szczeniak
3
To nie tylko dla wygody, ale także poprawiłoby bezpieczeństwo wyjątków w niektórych przypadkach. Zobacz na to odpowiedź Kerrek SB.
Piotr99
19

Chociaż nic nie stoi na przeszkodzie, aby napisać własnego pomocnika, uważam, że głównym powodem udostępnienia make_shared<T>w bibliotece jest to, że faktycznie tworzy on inny wewnętrzny typ wskaźnika wspólnego niż shared_ptr<T>(new T), który jest inaczej przydzielany, i nie ma sposobu, aby to osiągnąć bez dedykowanego pomocnik.

make_uniqueZ drugiej strony twoje opakowanie to zwykły cukier syntaktyczny wokół newwyrażenia, więc chociaż może wyglądać przyjemnie dla oka, nie przynosi niczego newna stół. Korekta: w rzeczywistości nie jest to prawdą: wywołanie funkcji w celu zawinięcia newwyrażenia zapewnia bezpieczeństwo wyjątków, na przykład w przypadku wywołania funkcji void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&). Posiadanie dwóch nieprzetworzonych względem siebie surowych newznaków oznacza, że ​​jeśli jedno nowe wyrażenie zawiedzie z wyjątkiem, drugie może przeciekać zasoby. Jeśli chodzi o to, dlaczego nie ma tego make_uniquew normie: zostało po prostu zapomniane. (Zdarza się to czasami. W std::cbeginstandardzie nie ma również globalnego, chociaż powinien istnieć.)

Zauważ też, że unique_ptrbierze drugi parametr szablonu, na który powinieneś jakoś zezwolić; różni się to od tego shared_ptr, który używa kasowania typu do przechowywania niestandardowych programów usuwających bez włączania ich do typu.

Kerrek SB
źródło
1
@FredOverflow: Współdzielony wskaźnik jest względnie skomplikowaną klasą; wewnętrznie zachowuje polimorficzny blok kontrolny odniesienia, ale istnieje kilka różnych rodzajów bloków kontrolnych. shared_ptr<T>(new T)używa jednego z nich, make_shared<T>()używa innego. Pozwalając, że jest to Dobra Rzecz, a wersja dzielona jest w pewnym sensie najlżejszym wspólnym wskaźnikiem, jaki można uzyskać.
Kerrek SB,
15
@FredOverflow: shared_ptrprzydziela blok pamięci dynamicznej, aby zachować liczbę i akcję „disposer” podczas tworzenia shared_ptr. Jeśli wskaźnik zostanie przekazany jawnie, musi utworzyć „nowy” blok, jeśli make_sharedgo użyjesz , możesz spakować swój obiekt i dane satelitarne w jednym bloku pamięci (jeden new), co spowoduje szybsze przydzielanie / zwalnianie, mniejszą fragmentację i (zwykle ) lepsze zachowanie pamięci podręcznej.
Matthieu M.,
2
Myślę, że muszę ponownie obejrzeć shared_ptr i znajomych ...
fredoverflow
4
-1 „Z drugiej strony twoje opakowanie make_unique to zwykły cukier syntaktyczny wokół nowego wyrażenia, więc chociaż może wyglądać przyjemnie dla oka, nie wnosi nic nowego do stołu.” jest źle. Daje możliwość wyjątkowego wywołania funkcji bezpiecznej. Nie daje jednak gwarancji; w tym celu musiałaby być klasa, aby można było zadeklarować formalne argumenty tej klasy (Herb opisał to jako różnicę między opt-in a opt-out).
Pozdrawiam i hth. - Alf
1
@ Cheersandhth.-Alf: Tak, to prawda. Od tego czasu to sobie uświadomiłem. Zmienię odpowiedź.
Kerrek SB
13

W C ++ 11 ...jest używany (w kodzie szablonu) również do „rozszerzenia pakietu”.

Wymagane jest, abyś używał go jako sufiksu wyrażenia zawierającego nierozwiniętą paczkę parametrów, a po prostu zastosuje to wyrażenie do każdego z elementów paczki.

Na przykład, opierając się na twoim przykładzie:

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)

To ostatnie jest niepoprawne.

Ponadto pakiet argumentów nie może być przekazany do funkcji nierozwiniętej. Nie jestem pewien co do zestawu parametrów szablonu.

Matthieu M.
źródło
1
Miło to widzieć. Dlaczego nie dołączyć również parametru szablonu variadic?
Kerrek SB,
@Kerrek: ponieważ nie jestem pewien co do jego dziwnej składni i nie wiem, czy wiele osób grało. Zatrzymam się więc na tym, co wiem. Może to wymagać wpisu FAQ w języku C ++, jeśli ktoś był wystarczająco zmotywowany i posiadał wystarczającą wiedzę, ponieważ składnia variadic jest kompletna.
Matthieu M.,
Składnia to std::forward<Args>(args)..., która rozwija się do forward<T1>(x1), forward<T2>(x2), ....
Kerrek SB,
1
: Myślę, że forwardtak naprawdę zawsze wymaga parametru szablonu, prawda?
Kerrek SB,
1
@Howard: racja! Wymuszanie tworzenia instancji powoduje ostrzeżenie ( ideone.com/GDNHb )
Matthieu M.,
5

Zainspirowany wdrożeniem Stephana T. Lavaveja, pomyślałem, że fajnie byłoby mieć make_unique, który obsługiwałby zakresy tablic, jest na githubie i chciałbym otrzymywać komentarze na jego temat. Pozwala to zrobić:

// 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); 
Nathan Binkert
źródło