std::unique_ptr
поддерживает массивы, например:
std::unique_ptr<int[]> p(new int[10]);
но нужно ли это? вероятно, удобнее использовать std::vector
или std::array
.
Вы найдете какое-либо использование для этой конструкции?
std::unique_ptr
поддерживает массивы, например:
std::unique_ptr<int[]> p(new int[10]);
но нужно ли это? вероятно, удобнее использовать std::vector
или std::array
.
Вы найдете какое-либо использование для этой конструкции?
У некоторых людей нет возможности использовать std::vector
, даже с распределителями. Некоторым людям нужен массив с динамическим размером, поэтому std::array
отсутствует. И некоторые люди получают свои массивы из другого кода, который, как известно, возвращает массив; и этот код не будет перезаписан, чтобы вернуть a 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[]>
не позволяет копироватьОбмен/перемещение
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 chars
Конструктор std::vector
и std::vector::resize()
будет инициализировать значение T
- но new
не сделает этого, если T
является POD.
См. Инициализированные по значению объекты в конструкторах С++ 11 и std::vector
Обратите внимание, что vector::reserve
здесь не является альтернативой: Доступ к необработанному указателю после std::vector:: безопасного резервирования?
По той же причине программист C может выбрать malloc
над calloc
.
An std::vector
можно скопировать вокруг, а unique_ptr<int[]>
позволяет выразить уникальную собственность на массив. std::array
, с другой стороны, требуется, чтобы размер определялся во время компиляции, что может быть невозможно в некоторых ситуациях.
Скотт Мейерс говорит об этом в "Эффективном современном С++"
Существование
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
может иметь указатель NULL.
Это пригодится при работе с C API, которые ожидают либо массив, либо 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());
}
Я использовал unique_ptr<char[]>
для реализации предустановленных пулов памяти, используемых в игровом движке. Идея состоит в том, чтобы использовать предварительно распределенные пулы памяти вместо динамических распределений для возврата результатов запросов на столкновение и других вещей, таких как физика частиц, без необходимости выделять/освобождать память на каждом кадре. Это довольно удобно для таких сценариев, когда вам нужны пулы памяти для распределения объектов с ограниченным сроком службы (обычно один, 2 или 3 кадра), которые не требуют логики уничтожения (только освобождение памяти).
Общую схему можно найти в некоторые вызовы 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...
...
В двух словах: это, безусловно, самый эффективный с точки зрения памяти.
A std::string
поставляется с указателем, длиной и буфером "короткой строки-оптимизации". Но моя ситуация в том, что мне нужно сохранить строку, которая почти всегда пуста, в структуре, в которой у меня сотни тысяч. В C я бы просто использовал char *
, и это было бы почти недействительным. Что тоже работает для С++, за исключением того, что char *
не имеет деструктора и не знает, как удалить его. В отличие от этого, std::unique_ptr<char[]>
удалит себя, когда он выходит из области видимости. Пустой std::string
занимает 32 байта, но пустой std::unique_ptr<char[]>
занимает 8 байтов, то есть точно размер его указателя.
Самый большой недостаток - каждый раз, когда я хочу знать длину строки, я должен называть strlen
на ней.
Я столкнулся с тем случаем, когда мне пришлось использовать std::unique_ptr<bool[]>
, который был в библиотеке HDF5 (библиотека для эффективного хранения двоичных данных, много использовала в науке). Некоторые компиляторы (Visual Studio 2015 в моем случае) обеспечивают сжатие std::vector<bool>
(используя 8 балов в каждом байте), что является катастрофой для чего-то вроде 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());
Мы все хотим, чтобы все было хорошо для нас. С++ - это в другое время.
new[]
std::vector
, например, чтобы предотвратить небрежное программирование от случайного ввода копий.Существует общее правило, согласно которому контейнеры С++ предпочтительнее, чем ваши собственные указатели. Это общее правило; он имеет исключения. Там больше; это просто примеры.
Чтобы ответить, люди думают, что вам нужно "использовать" vector
вместо unique_ptr
У меня есть случай в программировании CUDA на GPU, когда вы выделяете память на устройстве, вы должны пойти на массив указателей (с cudaMalloc
).
Затем, когда вы извлекаете эти данные в Host, вы должны пойти снова для указателя, а unique_ptr
- отлично обрабатывать указатель.
Дополнительная стоимость преобразования double*
в vector<double>
не нужна и приводит к потере perf.
unique_ptr<char[]>
можно использовать там, где требуется производительность C и удобство С++. Подумайте, что вам нужно работать с миллионами (ок, миллиарды, если вы еще не доверяете) строк. Хранение каждого из них в отдельном объекте string
или vector<char>
было бы катастрофой для подпрограмм управления памятью (кучей). Особенно, если вам нужно много раз выделять и удалять разные строки.
Однако вы можете выделить один буфер для хранения многих строк. Вам не понравится char* buffer = (char*)malloc(total_size);
по понятным причинам (если не очевидно, найдите "зачем использовать smart ptrs" ). Вам лучше unique_ptr<char[]> buffer(new char[total_size]);
По аналогии, те же соображения производительности и удобства применимы к данным не 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<...>
, соответственно - в результате будет предпринято попытка использования неполного типа, который является ошибкой времени компиляции.
Если вам нужен динамический массив объектов, несовместимых с копией, то умный указатель на массив - путь. Например, что, если вам нужен массив атомов.