Действительно ли безопасно вводить reinterpret_cast в std:: unique_ptr?

При использовании различных API, которые имеют структуры с переменным размером (структуры, которые должны быть выделены как byte [], а затем отбрасываются в структуру), было бы неплохо, если бы владелец unique_ptr мог указать на структуру, поскольку это то, что мы будем использовать.

Пример:

std::unique_ptr<VARIABLE_SIZE_STRUCT[]> v; 
v.reset(reinterpret_cast<VARIABLE_SIZE_STRUCT*>(new BYTE[bytesRequired]));

Это позволило `v предоставить представление самой структуре, что предпочтительнее, потому что нам не нужна вторая переменная, и мы не заботимся о указателе байта, кроме удаления.

Проблема заключается в возможности размытия указателя на литье (что делает его небезопасным для свободного). Я не вижу разумной причины, по которой компилятор изменил бы значение указателя на литье (так как наследования нет), но я слышал, что стандарт оставляет за собой право разыгрывать любой указатель на любой актерский состав, так как это касается стандартного кодирования, это подход выходит из окна, не так ли? Или есть какая-то причина, это безопасно? Есть ли способ, по крайней мере, static_assert, или каким-либо другим способом сделать его безопасным или чисто связанным с этим типом структуры?

Ответ 1

  • ваше распределение может не иметь выравнивания, необходимого для VARIABLE_SIZE_STRUCT

  • в выделенной памяти не было объекта VARIABLE_SIZE_STRUCT placement- new ed в нем - если вы позаботитесь об этом, логика деструктора по умолчанию unique_ptr должна найти объект ожидаемого объекта для уничтожения, но само освобождение не было бы выполнено с помощью delete [] на BYTE* - чтобы определить поведение, которое вам пришлось бы настроить, для того, чтобы вызывать первый ~VARIABLE_SIZE_STRUCT(), затем delete[]...

Если вас беспокоит "thunking", вы можете сделать проверку во время выполнения:

BYTE* p;
v.reset(reinterpret_cast<VARIABLE_SIZE_STRUCT*>(p = new BYTE[bytesRequired]));
assert(reinterpret_cast<BYTE*>(v.get()) == p);

Фон на этом - 5.2.10/7:

Указатель объекта может быть явно преобразован в указатель объекта другого типа. Когда prvalue v типа указателя объекта преобразуется в тип указателя объекта "указатель на cv T", результатом является static_cast<cvT*>(static_cast<cv void*>(v)). Преобразование prvalue типа "указатель на T1" в тип "указатель на T2" (где T1 и T2 являются типами объектов и где требования к выравниванию T2 не являются более строгими, чем те из T1) и обратно к исходному типу дает исходное значение указателя.

Итак, если требования к выравниванию для VARIABLE_SIZE_STRUCT более строгие, чем для BYTE, вам не гарантируется получение исходного указателя с помощью reinterpret_cast<BYTE*>.

Ответ 2

Вы правы, это небезопасно. Однако можно сделать это безопасным.

Стандарт гарантирует, что если вы reinterpret_cast на другой тип, затем вернитесь к исходному типу, вы вернете исходное значение.

Вы можете использовать это вместе с пользовательским удалением, чтобы убедиться, что ваш внутренний указатель возвращается к типу, который он был назначен, прежде чем освобождать его.

auto deleter = [](VARIABLE_SIZE_STRUCT* ptr) 
{ 
    delete[] reinterpret_cast<uint8_t*>(ptr); 
}; 

std::unique_ptr<VARIABLE_SIZE_STRUCT, decltype(deleter)> v
    (reinterpret_cast<VARIABLE_SIZE_STRUCT*>(new uint8_t[256]), deleter);

На этом этапе вам, вероятно, лучше создать собственную оболочку и не использовать unique_ptr.