std::unique_ptr
obsługuje tablice, na przykład:
std::unique_ptr<int[]> p(new int[10]);
ale czy jest to potrzebne? prawdopodobnie jest wygodniejszy w użyciu std::vector
lub std::array
.
Czy znajdujesz zastosowanie dla tego konstruktu?
c++
c++11
smart-pointers
unique-ptr
bagnisko
źródło
źródło
std::shared_ptr<T[]>
, ale powinno być i prawdopodobnie będzie w C ++ 14, jeśli ktokolwiek będzie miał problem z napisaniem propozycji. W międzyczasie zawsze jestboost::shared_array
.std::shared_ptr
<T []> jest teraz w c ++ 17.Odpowiedzi:
Niektóre osoby nie mają luksusu korzystania
std::vector
, nawet z przydzielającymi. Niektóre osoby potrzebują dynamicznej tablicy, więc niestd::array
ma. Niektóre osoby pobierają tablice z innego kodu, o którym wiadomo, że zwraca tablicę; i ten kod nie zostanie przepisany w celu zwróceniavector
czegoś lub czegoś.Pozwalając
unique_ptr<T[]>
, zaspokajasz te potrzeby.Krótko mówiąc, używasz,
unique_ptr<T[]>
kiedy potrzebujesz . Kiedy alternatywy po prostu nie będą dla ciebie działać. To narzędzie ostateczności.źródło
vector
”. Możesz spierać się, czy są to rozsądne wymagania, czy nie, ale nie możesz zaprzeczyć, że one istnieją .std::vector
gdyby mógłstd::unique_ptr
.unique_ptr
nich, ale takie projekty naprawdę istnieją.Są kompromisy i wybierasz rozwiązanie, które pasuje do tego, co chcesz. Z czubka mojej głowy:
Początkowy rozmiar
vector
iunique_ptr<T[]>
zezwól na określenie rozmiaru w czasie wykonywaniaarray
pozwala tylko określić rozmiar w czasie kompilacjiZmiana rozmiaru
array
iunique_ptr<T[]>
nie zezwalaj na zmianę rozmiaruvector
robiPrzechowywanie
vector
iunique_ptr<T[]>
przechowuj dane poza obiektem (zwykle na stercie)array
przechowuje dane bezpośrednio w obiekcieBiurowy
array
ivector
zezwól na kopiowanieunique_ptr<T[]>
nie zezwala na kopiowanieZamień / przenieś
vector
iunique_ptr<T[]>
mieć O (1)swap
operacje czasu i ruchuarray
ma O (n)swap
operacji ruchu i czasu , gdzie n jest liczbą elementów w tablicyUnieważnienie wskaźnika / odwołania / iteratora
array
zapewnia, że wskaźniki, referencje i iteratory nigdy nie zostaną unieważnione, gdy obiekt jest aktywny, nawet naswap()
unique_ptr<T[]>
nie ma iteratorów; wskaźniki i referencje są unieważniane tylko wtedy,swap()
gdy obiekt jest aktywny. (Po zamianie wskaźniki wskazują na tablicę, którą zamieniłeś, więc nadal są „prawidłowe” w tym sensie).vector
może unieważnić wskaźniki, referencje i iteratory przy każdej realokacji (i zapewnia pewne gwarancje, że realokacja może nastąpić tylko w przypadku niektórych operacji).Zgodność z pojęciami i algorytmami
array
ivector
oba są konteneramiunique_ptr<T[]>
nie jest konteneremMuszę przyznać, że wygląda to na okazję do pewnego refaktoryzacji dzięki projektowi opartemu na zasadach.
źródło
vector
. Następnie zwiększasz rozmiar lub pojemność,vector
tak że wymusza to realokację. Wówczas iterator, wskaźnik lub odwołanie nie wskazują już tego elementuvector
. To właśnie rozumiemy przez „unieważnienie”. Ten problem się nie zdarzaarray
, ponieważ nie ma „realokacji”. Właściwie właśnie zauważyłem jakiś szczegół i zredagowałem go, aby pasował.unique_ptr<T[]>
ponieważ nie ma realokacji. Ale oczywiście, gdy tablica wykracza poza zakres, wskaźniki do określonych elementów nadal będą unieważniane.T[]
, rozmiar (lub równoważna informacja) musi gdzieś wisieć,operator delete[]
aby poprawnie zniszczyć elementy tablicy. Byłoby miło, gdyby programista miał do tego dostęp.Jednym z powodów, dla których możesz użyć,
unique_ptr
jest to, że nie chcesz ponosić kosztów w czasie wykonywania inicjalizacji wartości tablicy.std::vector
Konstruktor istd::vector::resize()
będą wartości inicjalizacjiT
- alenew
nie zrobi tego, jeśliT
jest POD.Zobacz Obiekty inicjowane przez wartość w C ++ 11 i konstruktor std :: vector
Zauważ, że
vector::reserve
tutaj nie ma alternatywy: czy dostęp do surowego wskaźnika po std :: vector :: Reserve jest bezpieczny?To z tego samego powodu programista C może zdecydować
malloc
sięcalloc
.źródło
std::vector
w niestandardowym podzielnika który unika budowę typów, które sąstd::is_trivially_default_constructible
i niszczenie obiektów, którestd::is_trivially_destructible
, choć ściśle ten narusza standard C ++ (ponieważ takie typy nie są domyślnie zainicjowane).std::unique_ptr
nie zapewnia sprawdzanie zakresu przeciwieństwie do wielustd::vector
implementacjach.std::vector
jest wymagany przez Standard do sprawdzenia granic.at()
. Myślę, że miałeś na myśli, że niektóre implementacje mają tryby debugowania, które również się sprawdzają.operator[]
, ale uważam, że jest to bezużyteczne do pisania dobrego, przenośnego kodu.std::vector
Mogą być kopiowane dookoła, natomiastunique_ptr<int[]>
umożliwia wyrażanie unikalną własność tablicy.std::array
z drugiej strony wymaga określenia rozmiaru w czasie kompilacji, co może być niemożliwe w niektórych sytuacjach.źródło
unique_ptr
zamiastshared_ptr
. Czy coś brakuje?unique_ptr
nie tylko zapobiega przypadkowemu niewłaściwemu użyciu. Jest również mniejszy i niższy narzut niżshared_ptr
. Chodzi o to, że chociaż fajnie jest mieć semantykę w klasie, która zapobiega „niewłaściwemu użyciu”, nie jest to jedyny powód, aby używać określonego typu. Ivector
jest o wiele bardziej przydatny jako pamięć tablicowa niżunique_ptr<T[]>
, jeśli bez żadnego innego powodu niż fakt, że ma rozmiar .vector
naunique_ptr<T[]>
ile to możliwe, zamiast po prostu mówiąc: „nie można skopiować go”, a więc wybraćunique_ptr<T[]>
, gdy nie chcesz kopii. Powstrzymanie kogoś przed zrobieniem czegoś złego niekoniecznie jest najważniejszym powodem wyboru klasy.std::vector
ma więcej narzutów niż astd::unique_ptr
- używa ~ 3 wskaźników zamiast ~ 1.std::unique_ptr
blokuje kopiowanie konstrukcji, ale umożliwia przenoszenie konstrukcji, która jeśli semantycznie dane, z którymi pracujesz, mogą być tylko przenoszone, ale nie kopiowane, infekujeclass
zawierające je dane. Operacja na niepoprawnych danych faktycznie pogarsza klasę kontenera, a „po prostu nie używaj jej” nie zmywa wszystkich grzechów. Konieczność umieszczenia każdego wystąpieniastd::vector
w klasie, w której ręcznie wyłączasz,move
powoduje ból głowy.std::unique_ptr<std::array>
posiadasize
.Scott Meyers ma to do powiedzenia w Effective Modern C ++
Myślę, że odpowiedź Charlesa Salvii jest istotna:
std::unique_ptr<T[]>
jest to jedyny sposób na zainicjowanie pustej tablicy, której rozmiar nie jest znany w czasie kompilacji. Co Scott Meyers miałby do powiedzenia na temat motywacji do korzystaniastd::unique_ptr<T[]>
?źródło
vector
stackoverflow.com/a/24852984/2436175 .W przeciwieństwie do
std::vector
istd::array
,std::unique_ptr
może posiadać wskaźnik NULL.Jest to przydatne podczas pracy z interfejsami API języka C, które oczekują tablicy lub wartości NULL:
źródło
Użyłem
unique_ptr<char[]>
do realizacji zdefiniowanej przez pule pamięci wykorzystywane w silniku gry. Chodzi o to, aby zapewnić używane wstępnie przydzielone pule pamięci zamiast dynamicznych przydziałów do zwracania wyników żądań kolizji i innych rzeczy, takich jak fizyka cząstek, bez konieczności przydzielania / zwalniania pamięci w każdej ramce. Jest to dość wygodne w tego rodzaju scenariuszach, w których potrzebujesz pul pamięci do przydzielania obiektów o ograniczonym czasie życia (zwykle jedna, 2 lub 3 klatki), które nie wymagają logiki niszczenia (tylko dezalokacja pamięci).źródło
W niektórych wywołaniach API Windows Win32 można znaleźć wspólny wzorzec , w którym użycie
std::unique_ptr<T[]>
może się przydać, np. Gdy nie wiesz dokładnie, jak duży powinien być bufor wyjściowy podczas wywoływania niektórych API Win32 (które zapisują niektóre dane w środku ten bufor):źródło
std::vector<char>
w tych przypadkach.Napotkałem przypadek, w którym musiałem użyć
std::unique_ptr<bool[]>
, który był w bibliotece HDF5 (biblioteka do wydajnego przechowywania danych binarnych, bardzo często używana w nauce). Niektóre kompilatory (w moim przypadku Visual Studio 2015) zapewniają kompresjęstd::vector<bool>
(przy użyciu 8 booli w każdym bajcie), co jest katastrofą dla czegoś takiego jak HDF5, który nie dba o tę kompresję. Dziękistd::vector<bool>
HDF5 w końcu odczytywał śmieci z powodu tej kompresji.Zgadnij, kto był tam na ratunek, w przypadku, gdy
std::vector
nie działał, a ja musiałem przydzielić dynamiczną tablicę w czysty sposób? :-)źródło
W skrócie: jest to najbardziej wydajna pamięć.
A
std::string
zawiera wskaźnik, długość i bufor „optymalizacji ciągu krótkiego”. Ale moja sytuacja polega na tym, że muszę przechowywać ciąg prawie zawsze pusty, w strukturze, w której mam setki tysięcy. W C po prostu bym użyłchar *
i przez większość czasu byłby zerowy. Który działa również dla C ++, z wyjątkiem tego, żechar *
nie ma destruktora i nie wie, jak się usunąć. Natomiast astd::unique_ptr<char[]>
usunie się, gdy wyjdzie poza zakres. Pustystd::string
zajmuje 32 bajty, ale pustystd::unique_ptr<char[]>
zajmuje 8 bajtów, czyli dokładnie tyle, ile ma wskaźnik.Największym minusem jest to, że za każdym razem, gdy chcę poznać długość struny, muszę ją wezwać
strlen
.źródło
Aby odpowiedzieć ludziom myślącym, że „musisz” użyć
vector
zamiastunique_ptr
mam przypadek w programowaniu CUDA na GPU, kiedy przydzielasz pamięć w urządzeniu, musisz wybrać tablicę wskaźników (zcudaMalloc
). Następnie, podczas pobierania tych danych z hosta, musisz ponownie wybrać wskaźnik i możeszunique_ptr
łatwo obsługiwać wskaźnik. Dodatkowy koszt konwersjidouble*
navector<double>
jest zbędny i prowadzi do utraty perf.źródło
Dodatkowy powód, aby zezwolić i używać
std::unique_ptr<T[]>
, o którym do tej pory nie wspomniano w odpowiedziach: pozwala na zadeklarowanie w przód typu elementu tablicy.Jest to przydatne, gdy chcesz zminimalizować łańcuch
#include
instrukcje w nagłówkach (aby zoptymalizować wydajność kompilacji).Na przykład -
Dzięki powyższej strukturze kodu każdy może
#include "myclass.h"
i może korzystaćMyClass
bez konieczności uwzględniania wewnętrznych zależności implementacyjnych wymaganych przezMyClass::m_InternalArray
.Jeśli
m_InternalArray
zamiast tego został zadeklarowany odpowiednio jakostd::array<ALargeAndComplicatedClassWithLotsOfDependencies>
, lub astd::vector<...>
- wynikiem byłaby próba użycia niekompletnego typu, co jest błędem czasu kompilacji.źródło
class ALargeAndComplicatedClassWithLotsOfDependencies
. Logicznie więc nie powinieneś napotykać takich scenariuszy.Nie mogę wystarczająco mocno pogodzić się z duchem przyjętej odpowiedzi. „Narzędzie ostateczności”? Daleko stąd!
Według mnie jedną z najsilniejszych cech C ++ w porównaniu do C i niektórych innych podobnych języków jest zdolność do wyrażania ograniczeń, aby można je było sprawdzić w czasie kompilacji i zapobiec przypadkowemu niewłaściwemu użyciu. Podczas projektowania konstrukcji zadaj sobie pytanie, na jakie operacje powinna ona zezwalać. Wszelkie inne zastosowania powinny być zabronione, a najlepiej, jeśli takie ograniczenia można wdrożyć statycznie (w czasie kompilacji), aby niewłaściwe użycie skutkowało niepowodzeniem kompilacji.
Kiedy więc potrzebna jest tablica, odpowiedzi na następujące pytania określają jej zachowanie: 1. Czy jej rozmiar a) jest dynamiczny w czasie wykonywania lub b) statyczny, ale znany tylko w czasie wykonywania, lub c) statyczny i znany w czasie kompilacji? 2. Czy tablicę można przydzielić na stosie, czy nie?
I na podstawie odpowiedzi jest to, co uważam za najlepszą strukturę danych dla takiej tablicy:
Tak, myślę, że
unique_ptr<std::array>
należy również wziąć pod uwagę, i żadne z nich nie jest narzędziem ostateczności. Pomyśl, co najlepiej pasuje do Twojego algorytmu.Wszystkie z nich są kompatybilne ze zwykłymi interfejsami API języka C za pomocą surowego wskaźnika do tablicy danych (
vector.data()
/array.data()
/uniquePtr.get()
).PS Oprócz powyższych rozważań, istnieje również własność:
std::array
istd::vector
mają semantykę wartości (mają natywne wsparcie dla kopiowania i przekazywania według wartości), podczas gdyunique_ptr<T[]>
mogą być przenoszone tylko (wymusza pojedyncze prawo własności). Oba mogą być przydatne w różnych scenariuszach. Wręcz przeciwnie, zwykłe tablice statyczne (int[N]
) i zwykłe tablice dynamiczne (new int[10]
) nie oferują żadnego z nich i dlatego należy ich unikać, jeśli to możliwe - co powinno być możliwe w zdecydowanej większości przypadków. Jeśli to nie wystarczy, zwykłe tablice dynamiczne również nie oferują możliwości sprawdzenia ich wielkości - dodatkowa szansa na uszkodzenie pamięci i dziury w zabezpieczeniach.źródło
Mogą to być najodpowiedniejsze możliwe odpowiedzi, gdy można wcisnąć tylko jeden wskaźnik za pomocą istniejącego interfejsu API (pomyśl komunikat okna lub parametry wywołania zwrotnego związane z wątkami), które mają pewien pomiar czasu życia po „złapaniu” po drugiej stronie włazu, ale niezwiązane z kodem wywołującym:
Wszyscy chcemy, aby rzeczy były dla nas miłe. C ++ jest po raz drugi.
źródło
unique_ptr<char[]>
może być używany tam, gdzie chcesz wydajności C i wygody C ++. Pomyśl, że musisz operować milionami (ok, miliardami, jeśli jeszcze nie ufasz) ciągów. Przechowywanie każdego z nich w osobnymstring
lubvector<char>
obiekcie byłoby katastrofą dla procedur zarządzania pamięcią (stertą). Zwłaszcza jeśli trzeba wielokrotnie przydzielać i usuwać różne ciągi.Można jednak przydzielić jeden bufor do przechowywania tylu ciągów. Nie chciałbyś
char* buffer = (char*)malloc(total_size);
z oczywistych powodów (jeśli nie oczywiste, wyszukaj „dlaczego korzystać z inteligentnych plików ptrs”). Woliszunique_ptr<char[]> buffer(new char[total_size]);
Analogicznie te same względy dotyczące wydajności i wygody dotyczą nie-
char
danych (rozważ miliony wektorów / macierzy / obiektów).źródło
vector<char>
? Przypuszczam, że odpowiedź jest taka, ponieważ będą one inicjowane zerem podczas tworzenia bufora, a nie będą, jeśli użyjeszunique_ptr<char[]>
. Ale w Twojej odpowiedzi brakuje tego kluczowego samorodka.new[]
std::vector
, na przykład, aby zapobiec przypadkowemu wprowadzeniu kopii przez nieostrożnych programistówIstnieje ogólna zasada, że kontenery C ++ powinny być preferowane w porównaniu z własnymi wskaźnikami. Jest to ogólna zasada; ma wyjątki. Jest więcej; to tylko przykłady.
źródło
Jeśli potrzebujesz dynamicznej tablicy obiektów, których nie da się zbudować z kopii, dobrym wyborem jest inteligentny wskaźnik do tablicy. Na przykład co jeśli potrzebujesz tablicy atomowej.
źródło