Есть ли использование unique_ptr с массивом?

std::unique_ptr поддерживает массивы, например:

std::unique_ptr<int[]> p(new int[10]);

но нужно ли это? вероятно, удобнее использовать std::vector или std::array.

Вы найдете какое-либо использование для этой конструкции?

Ответ 1

У некоторых людей нет возможности использовать std::vector, даже с распределителями. Некоторым людям нужен массив с динамическим размером, поэтому std::array отсутствует. И некоторые люди получают свои массивы из другого кода, который, как известно, возвращает массив; и этот код не будет перезаписан, чтобы вернуть a vector или что-то еще.

Разрешая unique_ptr<T[]>, вы обслуживаете эти потребности.

Короче говоря, вы используете unique_ptr<T[]>, когда вам нужно. Когда альтернативы просто не сработают для вас. Это инструмент последней инстанции.

Ответ 2

Есть компромиссы, и вы выбираете решение, которое соответствует тому, что вы хотите. Сверху моей головы:

Исходный размер

  • 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[]> не является контейнером

Я должен признать, что это похоже на возможность рефакторинга с политическим дизайном.

Ответ 3

Одной из причин, по которой вы можете использовать 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.

Ответ 4

An std::vector можно скопировать вокруг, а unique_ptr<int[]> позволяет выразить уникальную собственность на массив. std::array, с другой стороны, требуется, чтобы размер определялся во время компиляции, что может быть невозможно в некоторых ситуациях.

Ответ 5

Скотт Мейерс говорит об этом в "Эффективном современном С++"

Существование std::unique_ptr для массивов должно представлять для вас только интеллектуальный интерес, поскольку std::array, std::vector, std::string - это практически всегда лучший выбор структуры данных, чем исходные массивы. О единственной ситуации, которую я могу себе представить, когда смысл std::unique_ptr<T[]> будет иметь смысл, - это когда вы используете C-подобный API, который возвращает необработанный указатель на массив кучи, который вы принимаете на себя.

Я думаю, что ответ Чарльза Сальвии уместен: std::unique_ptr<T[]> - единственный способ инициализировать пустой массив, размер которого неизвестен во время компиляции. Что Скотт Мейерс должен сказать об этой мотивации для использования std::unique_ptr<T[]>?

Ответ 6

В отличие от 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());
}

Ответ 7

Я использовал unique_ptr<char[]> для реализации предустановленных пулов памяти, используемых в игровом движке. Идея состоит в том, чтобы использовать предварительно распределенные пулы памяти вместо динамических распределений для возврата результатов запросов на столкновение и других вещей, таких как физика частиц, без необходимости выделять/освобождать память на каждом кадре. Это довольно удобно для таких сценариев, когда вам нужны пулы памяти для распределения объектов с ограниченным сроком службы (обычно один, 2 или 3 кадра), которые не требуют логики уничтожения (только освобождение памяти).

Ответ 8

Общую схему можно найти в некоторые вызовы 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...
...

Ответ 9

В двух словах: это, безусловно, самый эффективный с точки зрения памяти.

A std::string поставляется с указателем, длиной и буфером "короткой строки-оптимизации". Но моя ситуация в том, что мне нужно сохранить строку, которая почти всегда пуста, в структуре, в которой у меня сотни тысяч. В C я бы просто использовал char *, и это было бы почти недействительным. Что тоже работает для С++, за исключением того, что char * не имеет деструктора и не знает, как удалить его. В отличие от этого, std::unique_ptr<char[]> удалит себя, когда он выходит из области видимости. Пустой std::string занимает 32 байта, но пустой std::unique_ptr<char[]> занимает 8 байтов, то есть точно размер его указателя.

Самый большой недостаток - каждый раз, когда я хочу знать длину строки, я должен называть strlen на ней.

Ответ 10

Я столкнулся с тем случаем, когда мне пришлось использовать std::unique_ptr<bool[]>, который был в библиотеке HDF5 (библиотека для эффективного хранения двоичных данных, много использовала в науке). Некоторые компиляторы (Visual Studio 2015 в моем случае) обеспечивают сжатие std::vector<bool> (используя 8 балов в каждом байте), что является катастрофой для чего-то вроде HDF5, который не заботится об этом сжатии. С помощью std::vector<bool> HDF5 в конечном итоге считывал мусор из-за этого сжатия.

Угадайте, кто был там для спасения, в случае, когда std::vector не работал, и мне нужно было выделить динамический массив чисто?: -)

Ответ 11

Они могут быть самым правильным ответом, когда вы можете только вытолкнуть один указатель через существующий API (сообщение в окне сообщения или связанные с потоком параметры обратного вызова), которые имеют определенный срок жизни после того, как их "поймали" с другой стороны люк, но который не связан с вызывающим кодом:

unique_ptr<byte[]> data = get_some_data();

threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); },
                      data.release());

Мы все хотим, чтобы все было хорошо для нас. С++ - это в другое время.

Ответ 12

  • Вам нужна ваша структура, чтобы содержать только указатель на причины совместимости с двоичными файлами.
  • Вам необходимо взаимодействовать с API, который возвращает память, выделенную с помощью new[]
  • У вашей фирмы или проекта есть общее правило против использования std::vector, например, чтобы предотвратить небрежное программирование от случайного ввода копий.
  • Вы хотите, чтобы невнимательные программисты случайно вводили копии в этом экземпляре.

Существует общее правило, согласно которому контейнеры С++ предпочтительнее, чем ваши собственные указатели. Это общее правило; он имеет исключения. Там больше; это просто примеры.

Ответ 13

Чтобы ответить, люди думают, что вам нужно "использовать" vector вместо unique_ptr У меня есть случай в программировании CUDA на GPU, когда вы выделяете память на устройстве, вы должны пойти на массив указателей (с cudaMalloc). Затем, когда вы извлекаете эти данные в Host, вы должны пойти снова для указателя, а unique_ptr - отлично обрабатывать указатель. Дополнительная стоимость преобразования double* в vector<double> не нужна и приводит к потере perf.

Ответ 14

unique_ptr<char[]> можно использовать там, где требуется производительность C и удобство С++. Подумайте, что вам нужно работать с миллионами (ок, миллиарды, если вы еще не доверяете) строк. Хранение каждого из них в отдельном объекте string или vector<char> было бы катастрофой для подпрограмм управления памятью (кучей). Особенно, если вам нужно много раз выделять и удалять разные строки.

Однако вы можете выделить один буфер для хранения многих строк. Вам не понравится char* buffer = (char*)malloc(total_size); по понятным причинам (если не очевидно, найдите "зачем использовать smart ptrs" ). Вам лучше unique_ptr<char[]> buffer(new char[total_size]);

По аналогии, те же соображения производительности и удобства применимы к данным не char (рассмотрите миллионы векторов/матриц/объектов).

Ответ 15

Еще одна причина, позволяющая разрешать и использовать 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<...>, соответственно - в результате будет предпринято попытка использования неполного типа, который является ошибкой времени компиляции.

Ответ 16

Если вам нужен динамический массив объектов, несовместимых с копией, то умный указатель на массив - путь. Например, что, если вам нужен массив атомов.