трансляция через void * вместо использования reinterpret_cast

Я читаю книгу и обнаружил, что 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 используется, но я никогда не видел, чтобы это использовалось таким образом


person sinek    schedule 07.12.2009    source источник
comment
Спасибо, что спросили об этом. Кажется, ответ зарыт глубоко в Стандарте. (несколько раз спрашивал его в usenet, никто не мог дать гарантии, что эта последовательность статического приведения делает лучше). Здесь должен быть что-то: C ++ 0x добавил некоторую формулировку в спецификацию reinterpret_cast, которая переписывает ее при использовании с определенными типами в этой static_cast последовательности.   -  person Johannes Schaub - litb    schedule 08.12.2009
comment
@sinec Я должен спросить, почему вы считаете необходимым выполнять такие приведения? Я написал целую кучу кода на C ++ за эти годы без необходимости.   -  person    schedule 08.12.2009
comment
@Neil Butterworth Я работал над проектом (это было больше похоже на добавление новых функций к существующему коду), и было много сумасшедших составов, но это было неизбежно, поскольку мы не могли изменить устаревший код. Во всяком случае, я задал этот вопрос, так как читаю книгу, и я не мог найти этому объяснения.   -  person sinek    schedule 08.12.2009
comment
Которая Книга? В любом случае, возьми книгу получше.   -  person curiousguy    schedule 13.12.2011
comment
В книге были стандарты кодирования C ++ (Sutter / Alexandrescu) - часть безопасности типов (91-я глава).   -  person sinek    schedule 10.01.2013


Ответы (3)


Для типов, для которых разрешено такое приведение (например, если 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 [basic.types]:

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

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

3.9.2 [basic.compound]:

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

3.10 [basic.lval]:

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

  • ...
  • тип char или unsigned char.

4.10 [conv.ptr]:

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

5.2.9 [expr.static.cast]:

Может быть выполнена обратная любая стандартная последовательность преобразования (раздел 4), кроме преобразования lvalue-to-rvalue (4.1), массива-указателя (4.2), функции в указатель (4.3) и логического (4.12) преобразования. явно используя static_cast.

[EDIT] С другой стороны, у нас есть жемчужина:

9.2 [class.mem] / 17:

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

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

person Pavel Minaev    schedule 07.12.2009
comment
Но для результата static_cast также не дается никаких гарантий относительно преобразования в void* и обратно в другой тип. Он просто говорит, что значение указателя типа на объект, преобразованное в указатель на cv void и обратно к исходному типу указателя, будет иметь свое исходное значение. - person Johannes Schaub - litb; 08.12.2009
comment
См. Отредактированный ответ. Нет явного утверждения, что это нормально, но в стандарте есть многочисленные ссылки, которые строго подразумевают, что это так. Особо обратите внимание, что любой POD состоит из char объектов, и static_cast'ing указателя структуры POD на void* создает указатель void*, который указывает на первый такой объект char (поскольку в структурах POD не допускается начальное заполнение) . - person Pavel Minaev; 08.12.2009
comment
Т.е. если мы каким-то образом получим указатель на первый char представления объекта отдельно (без static_cast) и приведем его к void*, тогда Standard косвенно потребует, чтобы он был равен указателю на сам объект, приведенный к void*. Из этого следует, что точно так же, как мы можем выполнить обратное преобразование первого указателя на char* и заставить его работать, мы можем выполнить обратное преобразование последнего указателя на char* и заставить его работать (поскольку это то же значение указателя!). - person Pavel Minaev; 08.12.2009
comment
... однако см. также самое последнее изменение. Это невероятно запутано, и теперь я склонен сказать, что, исходя из логики моих предыдущих комментариев, и static_cast, и reinterpret_cast фактически гарантированно работают. - person Pavel Minaev; 08.12.2009
comment
-1 в стандарте есть многочисленные ссылки, которые строго подразумевают, что это так. Во всех этих ссылках даже не упоминается static_cast. - person curiousguy; 13.12.2011

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

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

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

person curiousguy    schedule 13.12.2011

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

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

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

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

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

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

person Charles Eli Cheese    schedule 08.12.2009
comment
Я согласен с большей частью того, что вы здесь сказали, но негативное клеймо на кастинге может быть слишком сильным. Приведение имеет свое место (как и большинство других языковых функций), и на основе недавней работы я сделал несколько полезных примеров, в том числе: использование const_cast в перегрузке неконстантного оператора для вызова перегрузки const (где логика такая же), вызов оператора преобразования для предотвращения дублирования кода, устранение неоднозначности (вывод небольших целочисленных литералов по сравнению с символьными литералами) и получение истинного адреса объекта с помощью перегруженного оператора адресации. Я полагаю, что для каждой особенности есть сезон. - person monkey0506; 25.01.2013