Вопрос

Я читаю книгу и нашел это reinterpret_cast не следует использовать напрямую, а скорее приводить к void * в сочетании с static_cast:

T1 * p1=...
void *pv=p1;
T2 * p2= static_cast<T2*>(pv);

Вместо того, чтобы:

T1 * p1=...
T2 * p2= reinterpret_cast<T2*>(p1);

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

Заранее благодарю

p.s.Я знаю, что это такое reinterpret_cast используется для, но я никогда не видел, чтобы это использовалось таким образом

Это было полезно?

Решение

Для типов , для которых такое приведение разрешено (напримересли T1 является стручковым типом и T2 является unsigned char), подход с static_cast четко определен Стандартом.

С другой стороны, reinterpret_cast полностью определяется реализацией - единственная гарантия, которую вы получаете за это, заключается в том, что вы можете привести тип указателя к любому другому типу указателя, а затем обратно, и вы получите исходное значение;а также вы можете привести тип указателя к целочисленному типу, достаточно большому, чтобы содержать значение указателя (которое варьируется в зависимости от реализации и вообще не обязательно должно существовать), а затем привести его обратно, и вы получите исходное значение.

Чтобы быть более конкретным, я просто процитирую соответствующие части Стандарта, выделив важные части:

5.2.10 [выражение.переосмысление.приведение]:

Сопоставление, выполняемое reinterpret_cast, является определенная реализация.[Примечание:это может привести, а может и нет, к представлению, отличному от исходного значения.] ...Указатель на объект может быть явно преобразован в указатель на объект другого типа.) За исключением того, что преобразование rvalue типа “указатель на T1” в тип “указатель на T2” (где T1 и T2 являются типами объектов и где требования к выравниванию T2 не более строгие, чем у T1) и обратно к его исходному типу приводит к исходному значению указателя, результат такого преобразования указателя не указан.

Итак, что-то вроде этого:

struct pod_t { int x; };
pod_t pod;
char* p = reinterpret_cast<char*>(&pod);
memset(p, 0, sizeof pod);

фактически не указан.

Объясняющий, почему static_cast с works все немного сложнее.Вот приведенный выше код, переписанный для использования static_cast который, я полагаю, гарантированно всегда будет работать так, как задумано Стандартом:

struct pod_t { int x; };
pod_t pod;
char* p = static_cast<char*>(static_cast<void*>(&pod));
memset(p, 0, sizeof pod);

Опять же, позвольте мне процитировать разделы Стандарта, которые в совокупности приводят меня к выводу, что вышеприведенное должно быть переносимым:

3.9[основные типы]:

Для любого объекта (отличного от подобъекта базового класса) POD типа T, независимо от того, содержит ли объект допустимое значение типа T, базовые байты (1.7), составляющие объект, могут быть скопированы в массив char или unsigned char.Если содержимое массива char или unsigned char копируется обратно в объект, объект впоследствии сохранит свое исходное значение.

Объектное представление объекта типа T представляет собой последовательность из N символов без знака Объекты принимается объектом типа T, где N равно sizeof(T).

3.9.2 [основное соединение]:

Объекты типа cv-qualified (3.9.3) или cv-unqualified void* (указатель на void), может использоваться для указания на объекты неизвестного типа.A void* должен быть способен удерживать указатель на любой объект. CV-квалифицированный или cv-неквалифицированный (3.9.3) void* должен соответствовать тем же требованиям к представительству и согласованию, что и cv-квалифицированный или cv-неквалифицированный char*.

3.10[базовый.lval]:

Если программа пытается получить доступ к сохраненному значению объекта через значение lvalue, отличное от одного из следующих типов, поведение не определено):

  • ...
  • тип символа char или unsigned char.

4.10 [продолжение ptr]:

Rvalue типа “указатель на cv T”, где T - тип объекта, может быть преобразовано в rvalue типа “указатель на cv void”. Результат преобразования “указателя на cv T” в “указатель на cv void” указывает на начало расположения хранилища, где находится объект типа T, как если бы объект был наиболее производным объектом (1.8) типа T (то есть не подобъектом базового класса).

5.2.9[выражение static.cast]:

Обратная любая стандартная последовательность преобразования (пункт 4), отличная от преобразований lvalue в rvalue (4.1), array-topointer (4.2), function-to-pointer (4.3) и boolean (4.12), может быть выполнена явно с использованием static_cast .

[ПРАВИТЬ] С другой стороны, у нас есть эта жемчужина:

9.2[class.mem]/17:

Указатель на объект POD-struct, соответствующим образом преобразованный с помощью reinterpret_cast, указывает на его начальный элемент (или, если этот элемент является битовым полем, то на единицу, в которой он находится) и наоборот.[Примечание:Там могло бы быть следовательно , быть безымянным заполнением внутри объекта POD-struct, но не в его начале, так как это необходимо для достижения соответствующего выравнивания.]

что, по-видимому, подразумевает , что reinterpret_cast между указателями каким-то образом подразумевается "один и тот же адрес".Пойди разберись.

Другие советы

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

Обе формы будут работать на практике.

reinterpret_cast более явно указывает на намерение и его следует отдать предпочтение.

Настоящая причина этого в том, как C++ определяет наследование, а также в указателях на члены.

В языке C указатель — это, по сути, просто адрес, как и должно быть.В C++ это должно быть сложнее из-за некоторых особенностей.

Указатели на члены на самом деле представляют собой смещение в классе, поэтому их приведение в стиле C всегда является катастрофой.

Если вы унаследовали несколько виртуальных объектов, которые также имеют конкретные части, это тоже катастрофа для стиля C.Однако именно так обстоит дело с множественным наследованием, которое вызывает все проблемы, поэтому вам все равно не следует его использовать.

Очень надеюсь, что вы вообще никогда не будете использовать эти случаи.Кроме того, если вы много кастуете, это еще один признак того, что вы напортачили в своем дизайне.

Единственный раз, когда я заканчиваю кастинг, это примитивы в областях, которые C++ решает, что они не совпадают, но там, где они, очевидно, должны быть.Что касается реальных объектов, каждый раз, когда вы хотите что-то применить, начните подвергать сомнению свой дизайн, потому что большую часть времени вам придется «программировать интерфейс».Конечно, вы не можете изменить работу сторонних API, поэтому у вас не всегда есть большой выбор.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top