reinterpret_cast для почти данных модуля (достаточно ли совместимости макета)

Я пытаюсь узнать о static_cast и reinterpret_cast.

Если я прав, стандарт (9.2.18) говорит, что reinterpret_cast для данных pod безопасен:

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

Мой вопрос заключается в том, как строго это интерпретировать. Достаточно ли, например, совместимости макета? И если нет, то почему?

Для меня следующий пример показывает пример, в котором строгая интерпретация «действительна только POD» кажется неправильной.

class complex_base  // a POD-class (I believe)
{
public:  
  double m_data[2];
};

class complex : public complex_base
{  //Not a POD-class (due to constructor and inheritance)
public:
  complex(const double real, const double imag); 
}

double* d = new double[4];
//I believe the following are valid because complex_base is POD
complex_base& cb1 = reinterpret_cast<complex_base&>(d[0]);  
complex_base& cb2 = reinterpret_cast<complex_base&>(d[2]);
//Does the following complete a valid cast to complex even though complex is NOT POD?
complex& c1 = static_cast<complex&>(cb1);
complex& c2 = static_cast<complex&>(cb2);

Кроме того, что может сломаться, если complex_base::m_data защищен (это означает, что complex_base не является pod)? [РЕДАКТИРОВАТЬ: и как мне защитить себя/обнаружить такие поломки]

Мне кажется, что компоновки-совместимости должно быть достаточно - но это не похоже на то, что говорит стандарт.

РЕДАКТИРОВАТЬ: Спасибо за ответы. Они также помогли мне найти это, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2342.htm


person Tom    schedule 20.02.2011    source источник
comment
@ClosureCowboy Оппс, отредактировал эту ошибку - кажется, я всегда печатаю перераспределение, а не переосмысливаю. (Да, я действительно запутался, когда гуглил, прежде чем публиковать этот вопрос)   -  person Tom    schedule 21.02.2011


Ответы (2)


Я считаю, что следующее верно, потому что complex_base - это POD

Ты неправ. d[0] не относится к первому члену объекта complex_base. Следовательно, его выравнивание может быть недостаточно хорошим для объекта complex_base, поэтому такое приведение небезопасно (и не разрешено текстом, который вы цитируете).

Завершает ли следующее правильное приведение к комплексу, даже если комплекс НЕ является POD?

cb1 и cb2 не указывают на подобъекты объекта типа complex, поэтому static_cast приводит к неопределенному поведению. См. 5.2.9p5 C++03.

Если lvalue типа "cv1 B" на самом деле является подобъектом объекта типа D, lvalue ссылается на объемлющий объект типа D. В противном случае результат приведения не определен.

Недостаточно, если только вовлеченные типы подходят друг другу. В тексте говорится об указателе, указывающем на объект POD-struct, и о lvalue, ссылающемся на определенный подобъект. оба комплекса и complex_base являются объектами стандартного макета. Спецификация С++ 0x говорит вместо текста, который вы цитируете:

Требование POD-ness слишком строгое?

Это другой вопрос, не касающийся вашего примера кода. Да, требование POD-ности слишком строго. В C++0x это было распознано, и было дано новое требование, более свободное, «стандартная компоновка». Я действительно думаю, что и complex и complex_base являются классами стандартной компоновки по определению C++0x. Спецификация С++ 0x говорит вместо текста, который вы цитируете:

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

Я интерпретирую это как возможность приведения указателя к double, который на самом деле указывает на член complex (член по наследству), для приведения к complex*. Класс стандартной компоновки — это класс, который либо не имеет базовых классов, содержащих нестатические данные, либо имеет только один базовый класс, содержащий нестатические данные. Таким образом, существует уникальный «начальный элемент».

person Johannes Schaub - litb    schedule 20.02.2011
comment
Спасибо, я запутался, почему d[0] не ссылается на complex_base::m_data[0] (я думал, что отрывок настаивал на том, что выравнивание от одного к другому одинаково) - Если у вас есть время, вы можете приведите пример модуля, где reinterpret_cast действителен для сравнения? - person Tom; 21.02.2011
comment
О, добавил предыдущий комментарий до того, как увидел ваше обновление - большое спасибо. Я не знал об обновлении С++ 0x. - person Tom; 21.02.2011

Что может сломать, так это то, что экземпляры класса, отличного от POD, могут иметь указатели vtable для реализации виртуальной диспетчеризации, если у них есть какие-либо виртуальные функции, включая виртуальный dtor. Указатель vtbl обычно будет первым членом класса, отличного от POD.

(Технически виртуальная диспетчеризация не обязательно должна быть реализована таким образом; практически это так. Вот почему Стандарт должен быть таким строгим в отношении того, что квалифицируется как тип POD.)

Честно говоря, я не уверен, почему просто наличие ctor («8.5.1 (1): «Агрегат — это массив или класс (пункт 9) без конструкторов, объявленных пользователем (12.1)») лишает что-то права быть POD. Но это так.

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

class complex_base  // a POD-class (I believe)
{
public:  
  double m_data[2];
  operator double*() {
    return m_data;
  }
};


complex_base b; // create a complex_base
double* p = b; 

Поскольку complex_base не является двойным*, компилятор C++ применит один (и только один) определяемый пользователем оператор преобразования, чтобы присвоить b значению p. Это означает, что p = b вызывает оператор преобразования, возвращая p = b.operator double*() (и обратите внимание, что это действительно допустимый синтаксис — вы можете напрямую вызывать операторы преобразования, а не должны), который, конечно, делает то, что он делает, в данном случае возвращает m_data.

Обратите внимание, что это сомнительно, так как теперь у нас есть прямой доступ к внутренностям b. На практике мы могли бы вернуть const double*, или копию, или умный «указатель» копирования при записи, или ....

Конечно, в этом случае m_data все равно является общедоступным, так что мы не хуже, чем если бы мы просто написали:

 double* p = b.m_data;

На самом деле нам немного лучше, потому что клиентам complex_base не нужно знать, как преобразовать его в двойное число.

person tpdi    schedule 20.02.2011
comment
Спасибо за очень поучительный ответ. Быстрый вопрос о вашем последнем примере: я хочу преобразовать из double* в double[] (и поэтому я подумал, что мне нужен reinterpret_cast) — я думал, что оператор преобразования работает в другом направлении. Я не могу заставить его работать, во всяком случае. - person Tom; 21.02.2011
comment
Что ж, double[] распадается на double* — по сути, если вы передаете имя массива, вы передаете указатель на первый элемент этого массива. При этом вы теряете информацию о длине массива. Чтобы узнать, как использовать оператор преобразования, см. мой отредактированный ответ. - person tpdi; 21.02.2011
comment
Спасибо за ваше обновление, но я хочу обратное преобразование: из двойного * в двойное []. - person Tom; 21.02.2011
comment
› Я хочу обратное преобразование: из double* в double[]. Чего нельзя получить: указатель не содержит информации о длине массива. Однако вы знаете, что этот массив всегда состоит из двух элементов, поэтому: вы можете обращаться с указателем как с именем массива: double* p = something(); р[0] = 1,0; р[1] = 2,0; потому что по определению имя_массива[индекс] равно *(имя_массива + индекс) - person tpdi; 22.02.2011