Должен ли я использовать static_cast или reinterpret_cast при приведении void* к чему-либо
Похоже, что static_cast и reinterpret_cast работают нормально для приведения void* к другому типу указателя. Есть ли веская причина отдать предпочтение одному над другим?
6 ответов
использованиеstatic_cast
: это самый узкий состав, который точно описывает, какое преобразование сделано здесь.
Существует заблуждение, что с помощью reinterpret_cast
было бы лучше, потому что это означает "полностью игнорировать безопасность типов и просто приводить от А к В".
Тем не менее, это на самом деле не описывает эффект reinterpret_cast
, Скорее, reinterpret_cast
имеет ряд значений, для всех которых гласит, что "отображение выполняется reinterpret_cast
определяется реализацией. "[5.2.10.3]
Но в частном случае литья из void*
в T*
отображение полностью четко определено стандартом; а именно, назначить тип указателю без указания типа без изменения его адреса.
Это причина предпочитать static_cast
,
Кроме того, и, возможно, более важным является тот факт, что каждое использование reinterpret_cast
является совершенно опасным, потому что он действительно конвертирует что-либо во что-то еще (для указателей), тогда как static_cast
гораздо более строгий, что обеспечивает лучший уровень защиты. Это уже спасло меня от ошибок, когда я случайно попытался привести один тип указателя к другому.
Это сложный вопрос. С одной стороны, Конрад делает отличное замечание относительно определения спецификации для reinterpret_cast, хотя на практике это, вероятно, делает то же самое. С другой стороны, если вы преобразуете между типами указателей (как это обычно бывает при индексировании в памяти с помощью char*, например), static_cast сгенерирует ошибку компилятора, и вам все равно придется использовать reinterpret_cast.
На практике я использую reinterpret_cast, потому что он более наглядно описывает цель операции приведения. Можно, конечно, сделать так, чтобы другой оператор назначил только реинтерпретации указателя (что гарантировало возвращение того же адреса), но в стандарте его нет.
Существует путаница с отображениями, определенными реализацией. Это что касается маппинга . Реализация может внутренне отображать как угодно, но в противном случае она должна предоставить другие гарантии. Результат reinterpret_cast не может просто произвольно указывать на то, что в противном случае реализация считала бы местоположением какого-либо другого объекта, хотя внешнее представление может отличаться. (Хотя преобразование в целое число и обратно будет иметь исходное значение, в определенных обстоятельствах указано). По сути, не имеет значения, возвращает ли переинтерпретированное приведение реализации одно и то же «место памяти»; все, что он возвращает, отображается на одно и то же «значение» . (Кстати, явно ответьте на случай, когда использование reinterpret_cast (char*/unsigned char*/std::byte*) для просмотра необработанного представления объекта является определенным поведением.)
Соответствующие стандарты управляют приведением void*:
static_cast:
Значение prvalue типа «указатель на cv1 void» может быть преобразовано в значение prvalue типа «указатель на cv2 T», где T — тип объекта, а cv2 — такое же или большее значение cv-квалификации, чем cv1. Если исходное значение указателя представляет адрес A байта в памяти и A не удовлетворяет требованию выравнивания T, то результирующее значение указателя не указано. В противном случае, если исходное значение указателя указывает на объект a, и существует объект b типа T (игнорируя cv-квалификацию), который является взаимопреобразуемым указателем (6.8.3) с a, результатом будет указатель на b. В противном случае значение указателя не изменяется при преобразовании. [Пример 3:
T* p1 = new T; const T* p2 = static_cast<const T*>(static_cast<void*>(p1)); bool b = p1 == p2; // b will have the value true.
— конец примера]
реинтрепрет_каст:
Указатель объекта может быть явно преобразован в указатель объекта другого типа.68 Когда prvalue v типа указателя объекта преобразуется в тип указателя объекта «указатель на cv T», результат
static_cast<cv T*>(static_cast<cv void*>(v))
Ключ в последнем предложении. Для целей этого вопроса void* cast (и при условии, что типы объектов соответствуют требованиям выравнивания, квалификации cv и являются безопасными указателями):
reinterpret_cast T* from void*
эквивалентноstatic_cast T* from void*
.
Но вы должны определенно, определенно абсолютно определенно использоватьstatic_cast
ни по какой другой причине страшный фольклор о reinterpret_cast и запутанности стандарта ISO может привести к ненужным разглагольствованиям коллег.
Основные принципыhttps://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ru-каламбур
Я предлагаю всегда использовать самый слабый актерский состав.
reinterpret_cast
может использоваться для наведения указателя на float
, Чем сильнее разрушается структура актерского состава, тем больше внимания требует его использование.
В случае char*
Я бы использовал бросок в стиле c, пока у нас не получится reinterpret_pointer_cast
потому что он слабее и ничего другого не достаточно.
Трансляция в и из
void*
использование и использование
reinterpret_cast
идентично. Смотрите ответ по ссылке . Но обычно
static_cast
предпочтительнее, потому что это более узкое и в целом (но не в данном конкретном случае) более безопасное преобразование.
Мои личные предпочтения основаны на грамотности кода:
void* data = something;
MyClass* foo = reinterpret_cast<MyClass*>(data);
foo->bar();
или же
typedef void* hMyClass; //typedef as a handle or reference
hMyClass = something;
const MyClass& foo = static_cast<MyClass&>(*hMyClass);
foo.bar();
Они оба делают то же самое в конце, но static_cast кажется более подходящим в промежуточном ПО, среде приложения, в то время как переосмысление приведено больше как то, что вы видели бы в низкоуровневой библиотеке IMHO.