Есть ли польза для уникального ptr с массивом?



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



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


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



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

736   16  

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 chars

The std::vector строитель std::vector::resize() будет значение-инициализировать T - но new не будет делать этого, если T - это стручок.

посмотреть Value-инициализированные объекты в C++11 и std::vector constructor

отметим, что vector::reserve здесь нет альтернативы:получает доступ к raw указатель после std::vector:: reserve safe?

это та же причина, по которой программист C может выбрать malloc over calloc.

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++, за исключением того, что a char * не имеет деструктора, и не знает, чтобы удалить себя. Напротив, a 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 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

    Ничего не найдено.