2009-08-11 2 views
5

Пусть у меня есть два класса с одинаковыми членами из двух различных библиотек:Casting между несвязанными конгруэнтными классами

namespace A { 
    struct Point3D { 
    float x,y,z; 
    }; 
} 

namespace B { 
    struct Point3D { 
    float x,y,z; 
    }; 
} 

Когда я пытаюсь кросс-кастинг, он работал:

A::Point3D pa = {3,4,5}; 
B::Point3D* pb = (B::Point3D*)&pa; 
cout << pb->x << " " << pb->y << " " << pb->z << endl; 

При каких обстоятельствах это гарантированно работать? Всегда? Обратите внимание, что было бы крайне нежелательно редактировать внешнюю библиотеку, чтобы добавить прагму выравнивания или что-то в этом роде. Я использую g ++ 4.3.2 на Ubuntu 8.10.

+0

Если у вас есть ра, почему это, что вам нужно пб? Поскольку любое место, где вы будете использовать pb, может просто иметь & pa. Я не уверен, что я ясно рассуждаю ... – ezpz

ответ

2

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

Пока они POD, все должно быть в порядке. http://en.wikipedia.org/wiki/Plain_old_data_structures

В соответствии со стандартом (1.8.5)

«Если это не является битовым поле (9.6), наиболее производным объект должен иметь ненулевой размер и должен занимать один или несколько байт . Субобъекты базового класса могут иметь нулевой размер. Объект POD5) тип (3.9) должен занимать смежные байты . "

Если они занимают смежные байт памяти, и они же структура, с другим именем, литая удастся

+0

Поскольку я прочитал эту цитату из стандарта, она не исключает заполнения или гарантирует, что две структуры будут использовать одинаковое заполнение. Я согласен, это почти наверняка будет работать на практике. – jalf

+0

Если обе структуры определены одинаково, а компилятор одинаковый, то заполнение должно быть идентичным. –

-2

Я точно знаю, что не будет работать:

  • как структура имеет диффере выравнивание;
  • скомпилирован с различными вариантами RTTI

может быть какой-то другой ...

+0

От http://en.wikipedia.org/wiki/RTTI: «RTTI доступен только для классов, которые являются полиморфными, что означает, что у них есть хотя бы один виртуальный метод». Не относится ли ваше отношение к POD? При каких условиях у них будет другое выравнивание? – Jann

+0

Это не так. Существует ключевое слово type C++, которое должно возвращать идентификатор для любого типа (независимо от использования), поэтому компилятору необходимо встраивать небольшой кусок в любую структуру. Более того, мы не знаем, как эта структура будет использоваться конечным программистом. Поэтому для одного экземпляра это преобразование будет правильным, но для массива может произойти сбой. – Dewfy

+0

компилятор делает * не * встраивает что-либо в любую структуру. Это легко проверить с помощью sizeof(). Вы можете легко создать структуру размера 1. Если вы создадите пустую структуру, она может даже иметь размер 0, если он используется как базовый класс. – jalf

0

Эта линия должна быть:

B::Point3D* pb = (B::Point3D*)&pa; 

Обратите внимание на &. Я думаю, что вы делаете reinterpret_cast между двумя указателями. Фактически вы можете reinterpret_cast любой тип указателя на другой, независимо от типа двух указателей. Но это небезопасно, а не портативно.

Например,

int x = 5; 
double* y = reinterpret_cast<double*>(&x); 

Вы только собираетесь с C-Style, Так что вторая линия фактически равна:

double* z = (double*)&x; 

Я просто ненавижу C-Style при забросе, потому что вы не можете сказать, цель броска от одного взгляда :)


При каких обстоятельствах это гарантированно работает?

Это не real литье между типами.Например,

int i = 5; 
float* f = reinterpret_cast<float*>(&i); 

Теперь f указывает на то же место, что указывает на i. Таким образом, преобразование не производится. Когда вы разыскиваете f, вы получите поплавок с тем же двоичным представлением целого числа i. Это четыре байта на моей машине.

+0

reinterpret_cast будет плохой идеей, так как технически не гарантировано приведет к указателю на тот же адрес. static_cast (static_cast ()) гарантирует указатель на тот же адрес. – jalf

+0

Спасибо за исправление. – Jann

2

Если две структуры POD начинаются с одной и той же последовательности элементов, стандарт гарантирует, что вы сможете свободно их получить через объединение. Вы можете сохранить A :: Point3D в объединении, а затем читать из члена B :: Point3D, если вы касаетесь только членов, которые являются частью исходной общей последовательности. (поэтому, если одна структура содержала int, int, int, float и другую, содержащую int, int, int, int, вам было разрешено иметь доступ только к трем первым ints).

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

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

+0

+1: Отличная точка. Было бы очень странным компилятором, который решил рассматривать POD-структуру по-разному, когда он вложен в объединение или нет. –

0

Следующая довольно безопасно:

namespace A { 
    struct Point3D { 
    float x,y,z; 
    }; 
} 

namespace B { 
    typedef A::Point3D Point3D; 
} 

int main() { 
    A::Point3D a; 
    B::Point3D* b = &a; 

    return 0; 
} 
Смежные вопросы