Mam klasę z członkiem unique_ptr.
class Foo {
private:
std::unique_ptr<Bar> bar;
...
};
Bar jest klasą strony trzeciej, która ma funkcje create () i niszczą ().
Gdybym chciał użyć std::unique_ptr
z nim jako samodzielnej funkcji, mógłbym zrobić:
void foo() {
std::unique_ptr<Bar, void(*)(Bar*)> bar(create(), [](Bar* b){ destroy(b); });
...
}
Czy można to zrobić std::unique_ptr
jako członek klasy?
c++
c++11
move-semantics
unique-ptr
huitlarc
źródło
źródło
std::unique_ptr<Bar, decltype(&destroy)> ptr_;
unique_ptr
(wszystkie muszą przechowywać wskaźnik funkcji wraz ze wskaźnikiem do rzeczywistych danych), wymaga przekazywania funkcji zniszczenia za każdym razem, nie może być wbudowana (ponieważ szablon nie może specjalizują się w określonej funkcji, tylko sygnatura) i muszą wywoływać funkcję za pomocą wskaźnika (droższe niż bezpośrednie wywołanie). Oba RICI i Deduplicator męska odpowiedzi uniknąć wszystkich tych kosztów przez specjalizującą się w funktora.Można to zrobić czysto, używając lambdy w C ++ 11 (testowane w G ++ 4.8.2).
Biorąc pod uwagę to wielokrotnego użytku
typedef
:template<typename T> using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;
Możesz pisać:
deleted_unique_ptr<Foo> foo(new Foo(), [](Foo* f) { customdeleter(f); });
Na przykład z
FILE*
:deleted_unique_ptr<FILE> file( fopen("file.txt", "r"), [](FILE* f) { fclose(f); });
Dzięki temu uzyskujesz korzyści z wyjątkowo bezpiecznego czyszczenia przy użyciu RAII, bez konieczności próbowania / łapania hałasu.
źródło
std::function
w definicji lub tym podobne?std::function
. W przeciwieństwie do tego rozwiązania można wstawić klasę lambda lub niestandardową, jak w zaakceptowanej odpowiedzi. Ale takie podejście ma przewagę w przypadku, gdy chcesz odizolować całą implementację w dedykowanym module.deleted_unique_ptr<Foo> foo(new Foo(), customdeleter);
jeśli jestcustomdeleter
zgodne z konwencją (zwraca void i przyjmuje surowy wskaźnik jako argument).Musisz tylko utworzyć klasę usuwającą:
struct BarDeleter { void operator()(Bar* b) { destroy(b); } };
i podaj go jako argument szablonu
unique_ptr
. Nadal będziesz musiał zainicjować unique_ptr w swoich konstruktorach:class Foo { public: Foo() : bar(create()), ... { ... } private: std::unique_ptr<Bar, BarDeleter> bar; ... };
O ile wiem, wszystkie popularne biblioteki C ++ implementują to poprawnie; ponieważ w
BarDeleter
rzeczywistości nie ma żadnego stanu, nie musi zajmować żadnego miejsca wunique_ptr
.źródło
struct BarDeleter
) tostd::unique_ptr
(std::unique_ptr<Bar, BarDeleter>
), która umożliwiastd::unique_ptr
konstruktorowi samodzielne utworzenie instancji Deleter. tj. następujący kod jest dozwolonystd::unique_ptr<Bar, BarDeleter> bar[10];
typedef std::unique_ptr<Bar, BarDeleter> UniqueBarPtr
unique_ptr
, nie ma potrzeby dostarczania wystąpienia elementu usuwającego podczas konstruowania) i dodaje korzyści w postaci możliwości użycia wstd::unique_ptr<Bar>
dowolnym miejscu bez konieczności pamiętania aby użyć specjalnegotypedef
lub jawnego dostawcy drugiego parametru szablonu. (Żeby było jasne, to dobre rozwiązanie, zagłosowałem za, ale zatrzymuje się o jeden krok przed bezproblemowym rozwiązaniem)O ile nie musisz mieć możliwości zmiany usuwania w czasie wykonywania, zdecydowanie zalecamy użycie niestandardowego typu usuwania. Na przykład, jeśli użyć wskaźnika funkcji dla Deleter,
sizeof(unique_ptr<T, fptr>) == 2 * sizeof(T*)
. Innymi słowy,unique_ptr
marnowana jest połowa bajtów obiektu.Pisanie niestandardowego narzędzia do pakowania w celu zawijania każdej funkcji jest jednak kłopotliwe. Na szczęście możemy napisać typ szablonu na funkcji:
Od C ++ 17:
template <auto fn> using deleter_from_fn = std::integral_constant<decltype(fn), fn>; template <typename T, auto fn> using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>; // usage: my_unique_ptr<Bar, destroy> p{create()};
Przed C ++ 17:
template <typename D, D fn> using deleter_from_fn = std::integral_constant<D, fn>; template <typename T, typename D, D fn> using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<D, fn>>; // usage: my_unique_ptr<Bar, decltype(destroy), destroy> p{create()};
źródło
deleter_from_fn
jest.Wiesz, używanie niestandardowego narzędzia do usuwania nie jest najlepszym rozwiązaniem, ponieważ będziesz musiał wspomnieć o tym w całym kodzie.
Zamiast tego, ponieważ możesz dodawać specjalizacje do klas na poziomie przestrzeni nazw,
::std
o ile są to typy niestandardowe i szanujesz semantykę, zrób to:Specjalizacja
std::default_delete
:template <> struct ::std::default_delete<Bar> { default_delete() = default; template <class U> constexpr default_delete(default_delete<U>) noexcept {} void operator()(Bar* p) const noexcept { destroy(p); } };
A może też
std::make_unique()
:template <> inline ::std::unique_ptr<Bar> ::std::make_unique<Bar>() { auto p = create(); if (!p) throw std::runtime_error("Could not `create()` a new `Bar`."); return { p }; }
źródło
std
otwiera zupełnie nową puszkę robaków. Zauważ również, że specjalizacjastd::make_unique
nie jest dozwolona po C ++ 20 (dlatego nie powinna być wykonywana wcześniej), ponieważ C ++ 20 zabrania specjalizacji rzeczy,std
które nie są szablonami klas (std::make_unique
jest szablonem funkcji). Zauważ, że prawdopodobnie skończysz z UB, jeśli wskaźnik przekazany dostd::unique_ptr<Bar>
nie został przydzielony zcreate()
, ale z innej funkcji alokacji.std::default_delete
spełnia wymagania oryginalnego szablonu. Wyobrażam sobie, żestd::default_delete<Foo>()(p)
byłby to prawidłowy sposób pisaniadelete p;
, więc gdybydelete p;
można było pisać (tj. JeśliFoo
jest kompletny), nie byłoby to takie samo zachowanie. Ponadto, jeślidelete p;
zapisFoo
byłby nieprawidłowy ( jest niekompletny), oznaczałoby to określenie nowego zachowaniastd::default_delete<Foo>
zamiast zachowania tego samego zachowania.make_unique
Specjalizacja jest problematyczne, ale ja na pewno wykorzystałstd::default_delete
przeciążenia (nie na matrycy zenable_if
, tylko dla struktur C jak OpenSSLBIGNUM
, które wykorzystują znaną funkcję zniszczenie, gdzie podklasy nie zdarzy), a to zdecydowanie najprostszy podejście, jak reszta twojego kodu może po prostu użyćunique_ptr<special_type>
bez konieczności przekazywania typu funktora jako szablonu naDeleter
całym świecie, ani używaćtypedef
/using
do nadania nazwy wspomnianemu typowi, aby uniknąć tego problemu.Możesz po prostu użyć
std::bind
funkcji zniszczenia.std::unique_ptr<Bar, std::function<void(Bar*)>> bar(create(), std::bind(&destroy, std::placeholders::_1));
Ale oczywiście możesz też użyć lambdy.
std::unique_ptr<Bar, std::function<void(Bar*)>> ptr(create(), [](Bar* b){ destroy(b);});
źródło