16 ответов:
некоторые люди не имеют роскошь использования
std::vector, даже с распределителей. Некоторым людям нужен массив динамического размера, поэтомуstd::arrayвыходит. И некоторые люди получают свои массивы из другого кода, который, как известно, возвращает массив; и этот код не будет переписан, чтобы вернутьvectorили что-то.разрешение
unique_ptr<T[]>, вы обслуживаете эти потребности.короче говоря, вы используете
unique_ptr<T[]>когда вы нужно для. Когда альтернативы просто не будет работать для вас. Это инструмент последней инстанции.
есть компромиссы, а вы выбираете решение, которое соответствует тому, что вы хотите. С верхней части моей головы:
исходный размер
vectorиunique_ptr<T[]>разрешить указывать размер во время выполненияarrayтолько позволяет указать размер во время компиляцииизменение размера
arrayиunique_ptr<T[]>Не разрешать изменение размераvectorделаетдля хранения
vectorиunique_ptr<T[]>хранить данные вне объекта (обычно в куче)arrayхранит данные непосредственно в объектекопирование
arrayиvectorразрешить копированиеunique_ptr<T[]>не позволяет копироватьSwap / move
vectorиunique_ptr<T[]>есть O (1) Времяswapи двигаться операцииarrayимеет O (n) времяswapи ход операций, где n-количество элементов в массивенедействительность указателя/ссылки/итератора
arrayгарантирует, что указатели, ссылки и итераторы никогда не будут признаны недействительными, пока объект находится в режиме реального времени, даже наswap()unique_ptr<T[]>не имеет итераторов; указатели и ссылки только недействительныswap()пока объект жив. (После замены, указатели указывают на массив, с которым вы поменялись, поэтому они все еще "действительны" в этом смысле.)vectorможет привести к недействительности указателей, ссылок и итераторов при любом перераспределении (и обеспечивает некоторые гарантии того, что перераспределение может произойти только при определенных операциях).совместимость с понятиями и алгоритмами
arrayиvectorоба контейнераunique_ptr<T[]>не Контейнеря должен признать, что это похоже на возможность для некоторого рефакторинга с дизайном на основе политики.
одна из причин, по которой вы можете использовать
unique_ptrэто если вы не хотите платить стоимость выполнения значение-инициализация массив.std::vector<char> vec(1000000); // allocates AND value-initializes 1000000 chars std::unique_ptr<char[]> p(new char[1000000]); // allocates storage for 1000000 charsThe
std::vectorстроительstd::vector::resize()будет значение-инициализироватьT- ноnewне будет делать этого, еслиT- это стручок.посмотреть Value-инициализированные объекты в C++11 и std::vector constructor
отметим, что
vector::reserveздесь нет альтернативы:получает доступ к raw указатель после std::vector:: reserve safe?это та же причина, по которой программист C может выбрать
mallocovercalloc.
An
std::vectorможно копировать вокруг, в то время какunique_ptr<int[]>позволяет выразить уникальное право собственности на массив.std::array, С другой стороны, требует размер будет определен во время компиляции, что может быть невозможно в некоторых ситуациях.
Скотт Мейерс имеет это сказать в эффективном современном C++
существование
std::unique_ptrибо массивы должны представлять для вас только интеллектуальный интерес, потому чтоstd::array,std::vector,std::stringпрактически всегда лучше, выбор структуры данных, чем сырые массивы. О единственной ситуации, которую я могу себе представить, когдаstd::unique_ptr<T[]>было бы разумно, если бы вы использовали C-подобный API, который возвращает необработанный указатель на массив кучи, который вы принимаете на себя ответственность из.Я думаю, что ответ Чарльза сальвии уместен, хотя: это
std::unique_ptr<T[]>это единственный способ инициализации пустого массива, размер которого неизвестен во время компиляции. Что бы Скотт Мейерс сказал об этой мотивации для использованияstd::unique_ptr<T[]>?
вопреки
std::vectorиstd::array,std::unique_ptrможет иметь нулевой указатель.
Это удобно при работе с API C, которые ожидают либо массив, либо NULL:void legacy_func(const int *array_or_null); void some_func() { std::unique_ptr<int[]> ptr; if (some_condition) { ptr.reset(new int[10]); } legacy_func(ptr.get()); }
общий шаблон можно найти в некоторые Windows Win32 API звонки, в которых использование
std::unique_ptr<T[]>может пригодиться, например, когда вы точно не знаете, насколько большим должен быть выходной буфер при вызове некоторого Win32 API (который будет записывать некоторые данные внутри этого буфера):// Buffer dynamically allocated by the caller, and filled by some Win32 API function. // (Allocation will be made inside the 'while' loop below.) std::unique_ptr<BYTE[]> buffer; // Buffer length, in bytes. // Initialize with some initial length that you expect to succeed at the first API call. UINT32 bufferLength = /* ... */; LONG returnCode = ERROR_INSUFFICIENT_BUFFER; while (returnCode == ERROR_INSUFFICIENT_BUFFER) { // Allocate buffer of specified length buffer.reset( BYTE[bufferLength] ); // // Or, in C++14, could use make_unique() instead, e.g. // // buffer = std::make_unique<BYTE[]>(bufferLength); // // // Call some Win32 API. // // If the size of the buffer (stored in 'bufferLength') is not big enough, // the API will return ERROR_INSUFFICIENT_BUFFER, and the required size // in the [in, out] parameter 'bufferLength'. // In that case, there will be another try in the next loop iteration // (with the allocation of a bigger buffer). // // Else, we'll exit the while loop body, and there will be either a failure // different from ERROR_INSUFFICIENT_BUFFER, or the call will be successful // and the required information will be available in the buffer. // returnCode = ::SomeApiCall(inParam1, inParam2, inParam3, &bufferLength, // size of output buffer buffer.get(), // output buffer pointer &outParam1, &outParam2); } if (Failed(returnCode)) { // Handle failure, or throw exception, etc. ... } // All right! // Do some processing with the returned information... ...
я использовал
unique_ptr<char[]>для реализации предварительно пулов памяти, используемых в игровом движке. Идея состоит в том, чтобы обеспечить предварительно выделенные пулы памяти, используемые вместо динамических распределений для возврата результатов запросов на столкновение и других вещей, таких как физика частиц, без необходимости выделять / освобождать память в каждом кадре. Это довольно удобно для такого рода сценариев, где вам нужны пулы памяти для выделения объектов с ограниченным временем жизни (обычно один, 2 или 3 кадра), которые не требуют уничтожения логика (только освобождение памяти).
в двух словах: это, безусловно, самая эффективная память.
A
std::stringпоставляется с указателем, длиной и буфером" short-string-optimization". Но моя ситуация заключается в том, что мне нужно хранить строку, которая почти всегда пуста, в структуре, которую у меня есть сотни тысяч. В C, я бы просто использоватьchar *, и это будет null большую часть времени. Что тоже работает на C++, за исключением того, что achar *не имеет деструктора, и не знает, чтобы удалить себя. Напротив, astd::unique_ptr<char[]>будет удалять себя, когда он выходит за рамки. Пустойstd::stringзанимает 32 байта, но пустойstd::unique_ptr<char[]>занимает 8 байт, то есть ровно на размер указателя.самый большой недостаток, каждый раз, когда я хочу знать длину строки, я должен позвонить
strlenна нем.
я столкнулся со случаем, когда мне пришлось использовать
std::unique_ptr<bool[]>, который был в библиотеке HDF5 (библиотека для эффективного хранения двоичных данных, много используется в науке). Некоторые компиляторы (Visual Studio 2015 в моем случае)обеспечивают сжатиеstd::vector<bool>(используя 8 bools в каждом байте), что является катастрофой для чего-то вроде HDF5, который не заботится об этом сжатии. Сstd::vector<bool>, HDF5 в конечном итоге читал мусор из-за этого сжатия.угадайте, кто был там спасите, в случае где
std::vectorне работает, и мне нужно было выделить динамический массив чисто? : -)
они могут быть самым правильным ответом, когда вы только можете ткнуть один указатель через существующий API (подумайте о сообщении окна или связанных с потоком параметрах обратного вызова), которые имеют некоторую меру времени жизни после того, как они" пойманы " на другой стороне штриховки, но которые не связаны с вызывающим кодом:
unique_ptr<byte[]> data = get_some_data(); threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); }, data.release());мы все хотим, чтобы все было хорошо для нас. C++ - это для других времен.
- вам нужно, чтобы ваша структура содержала только указатель по причинам двоичной совместимости.
- вам нужно взаимодействовать с API, который возвращает память, выделенную с
new[]- ваша фирма или проект имеет общее правило против использования
std::vector, например, чтобы не дать неосторожным программистам случайно ввести копии- вы хотите предотвратить нерадивых программистов от случайного введения копий в этом пример.
существует общее правило, что контейнеры C++ должны быть предпочтительнее, чем сворачивание с указателями. Это общее правило; у него есть исключения. Это еще не все; это просто примеры.
чтобы ответить людям, которые думают, что вы" должны " использовать
vectorвместоunique_ptrУ меня есть случай в программировании CUDA на GPU, когда вы выделяете память в устройстве, вы должны пойти на массив указателей (сcudaMalloc). Затем, при получении этих данных в Host, вы должны снова перейти к указателю иunique_ptrотлично справляется с указателем легко. Дополнительная стоимость конвертацииdouble*доvector<double>является излишним и приводит к потере производительности.
unique_ptr<char[]>может использоваться там, где вы хотите производительность C и удобство C++. Подумайте, что вам нужно работать с миллионами (хорошо, миллиардами, если вы еще не доверяете) строк. Хранение каждого из них в отдельномstringилиvector<char>объект будет катастрофой для процедур управления памятью (кучей). Особенно, если вам нужно выделить и удалить разные строки много раз.однако вы можете выделить один буфер для хранения такого количества строк. Тебе бы не понравилось
char* buffer = (char*)malloc(total_size);по понятным причинам (если не очевидно, поиск "зачем использовать смарт-ptrs"). Вы бы предпочлиunique_ptr<char[]> buffer(new char[total_size]);по аналогии, те же соображения производительности и удобства применяются к non-
charданные (рассмотрим миллионы векторов / матриц / объектов).
еще одна причина разрешить и использовать
std::unique_ptr<T[]>, который до сих пор не упоминался в ответах: он позволяет вам переадресовывать тип элемента массива.это полезно, когда вы хотите, чтобы свести к минимуму цепной
#includeоператоры в заголовках (для оптимизации производительности сборки.), например,
myclass.h:
class ALargeAndComplicatedClassWithLotsOfDependencies; class MyClass { ... private: std::unique_ptr<ALargeAndComplicatedClassWithLotsOfDependencies[]> m_InternalArray; };myclass.cpp:
#include "myclass.h" #include "ALargeAndComplicatedClassWithLotsOfDependencies.h" // MyClass implementation goes hereС выше структуры кода, любой может
#include "myclass.h"и использоватьMyClass, без необходимости включать внутренние зависимости реализации, требуемыеMyClass::m_InternalArray.если
m_InternalArrayвместо этого было объявлено какstd::array<ALargeAndComplicatedClassWithLotsOfDependencies>илиstd::vector<...>, соответственно-результатом будет попытка использования неполного типа, что является ошибкой времени компиляции.
Если вам нужен динамический массив объектов, которые не являются копируемыми, то умный указатель на массив - это путь. Например, что делать, если вам нужен массив атомики.
Comments