2009-12-07 10 views
35

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

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

Вместо:

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

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

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

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

+2

Спасибо, что спросили об этом. Кажется, что ответ полон в Стандарте. (несколько раз спросил его в usenet, никто не мог указать на гарантию того, что эта последовательность статического литья делает лучше). Там * есть *, чтобы быть чем-то: C++ 0x добавил некоторые формулировки спецификации 'reinterpret_cast', которая перезаписывает его при использовании с определенными типами в этой последовательности' static_cast'. –

+0

@sinec Я должен спросить, почему вы считаете необходимым выполнять такие приведения? На протяжении многих лет я написал множество нагрузок кода C++, не требуя этого. – 2009-12-07 21:53:50

+0

@Neil Butterworth Я работал над проектом (это было больше похоже на добавление новых функций в существующий код), и было много сумасшедших бросков, но это было неизбежно, поскольку мы не могли изменить устаревший код. Во всяком случае, я задал этот вопрос, так как я читаю книгу, и я не мог найти объяснения этого. – sinek

ответ

24

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

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

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

5.2.10 [expr.reinterpret.cast]:

Отображение выполняемых 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 несколько сложнее. Вот приведенный выше код переписан для использования 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 [основной.Типы]:

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

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

3.9.2 [basic.compound]:

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

3,10 [basic.lval]:

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

  • ...
  • полукокса или тип без знака символ.

4,10 [conv.ptr]:

Rvalue типа «указатель на сорта Т», где Т представляет собой тип объекта, может быть преобразован в RValue типа «указатель cv void. «Результат преобразования« указателя на cv T »в« указатель на cv void »указывает на начало места хранения, где находится объект типа T, как если бы объект был наиболее производным объектом (1.8) типа T (т. е. не подобъект базового класса).

5.2.9 [expr.static.cast]:

Обратные любой стандартной последовательности преобразования (пункт 4), отличные от именующего-к-Rvalue (4.1), массив-topointer (4.2), функция-to-pointer (4.3) и логические (4.12) преобразования могут выполняться явно с использованием static_cast.

[EDIT] С другой стороны, у нас есть этот драгоценный камень:

9,2 [class.mem]/17:

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

, который, по-видимому, подразумевает, чтомежду указателями как-то подразумевает «тот же адрес». Идите фигуру.

+1

Но для результата 'static_cast' больше не делается никаких гарантий относительно преобразования в' void * 'и обратно в * другой *. Он просто говорит: «Значение указателя типа для объекта, преобразованного в« указатель на cv void »и обратно к исходному типу указателя, будет иметь свое первоначальное значение». –

+0

См. Отредактированный ответ. Нет явного утверждения о том, что «это нормально», но в стандарте есть множество ссылок, которые сильно подразумевают, что это так. В частности, обратите внимание, что любой POD состоит из char _objects_, а static_cast - указатель структуры POD на 'void *' создает указатель 'void *', который указывает на первый такой объект char (поскольку в структурах POD не допускается начальное заполнение). –

+0

I.e.если мы каким-то образом получим указатель на первый символ представления объекта отдельно (без static_cast) и отбрасываем его на 'void *', тогда Standard косвенно требует, чтобы он был равен указателю на сам объект, который был введен в 'void *'. Из этого следует, что так же, как мы можем вернуть прежний указатель на символ «char *» и заставить его работать, поэтому мы можем «вернуть» последний указатель на «char *» и заставить его работать (поскольку это то же значение указателя!). –

3

Настоящая причина заключается в том, что C++ определяет наследование и из-за указателей элементов.

С помощью указателя C в значительной степени просто адрес, как и должно быть. В C++ он должен быть более сложным из-за некоторых его функций.

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

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

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

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

+0

Я согласен с тем, что вы сказали здесь, но отрицательное стигма при кастинге может быть немного. Кастинг имеет свое место (как и большинство других языковых функций), и на основе некоторой недавней работы, которую я сделал, некоторые полезные примеры включают: использование const_cast в перегрузке неконстантного оператора для вызова перегрузки const (где логика такая же) вызывая оператор преобразования, чтобы предотвратить дублирование кода, устраняя неоднозначность (выводящий мелкие целочисленные литералы против символьных литералов) и получая истинный адрес объекта с перегруженным адресом оператора. Кажется, у каждой особенности есть сезон. –

6

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

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

reinterpret_cast более подробно о намерениях и должен быть предпочтительнее.