Безопасность приведения между указателями двух одинаковых классов?

#c #pointers #casting

#c #указатели #Кастинг

Вопрос:

Допустим, у меня есть два разных класса, оба представляют данные 2D-координат одним и тем же внутренним способом, например, следующим:

 class LibA_Vertex{
    public:
    // ... constructors and various methods, operator overloads
    float x, y
};

class LibB_Vertex{
    public:
    // ... same usage and internal data as LibA, but with different methods
    float x, y
};


void foobar(){
    LibA_Vertex * verticesA = new LibA_Vertex[1000];
    verticesA[50].y = 9;
    LibB_Vertex * verticesB = reinterpret_cast<LibB_Vertex*>( vertexA );
    print(verticesB[50].y); // should output a "9"
};
  

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

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

Ответ №1:

В C 11 добавлена концепция, называемая layout-compatible, которая применяется здесь.

Два типа структуры стандартной компоновки (пункт 9) совместимы с компоновкой, если они имеют одинаковое количество нестатических элементов данных, а соответствующие нестатические элементы данных (в порядке объявления) имеют типы, совместимые с компоновкой (3.9).

где

Класс стандартной компоновки — это класс, который:

  • не имеет нестатических элементов данных типа нестандартного класса компоновки (или массива таких типов) или ссылки,
  • не имеет виртуальных функций (10.3) и виртуальных базовых классов (10.1),
  • имеет одинаковый контроль доступа (пункт 11) для всех нестатических элементов данных,
  • не имеет базовых классов нестандартной компоновки,
  • либо не имеет нестатических элементов данных в самом производном классе и не более одного базового класса с нестатическими элементами данных, либо не имеет базовых классов с нестатическими элементами данных, и
  • не имеет базовых классов того же типа, что и первый нестатический элемент данных.

Структура стандартной компоновки — это класс стандартной компоновки, определенный с помощью class-key struct или class-key class .

Объединение стандартной компоновки — это класс стандартной компоновки, определенный с помощью class-key union .

Наконец

Указатели на cv-qualified и cv-unqualified версии (3.9.3) типов, совместимых с макетом, должны иметь одинаковые требования к представлению значений и выравниванию (3.11).

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

Комментарии:

1. Другим четко определенным способом выполнения этих преобразований является использование объединения с общей начальной последовательностью (как разрешено §9.2/19 ).

2. О! Что ж, C 11 на помощь. Я просто надеюсь, что это было среди того, что VS2010 решил добавить, когда он выбрал вишню через стандарт.

3. @Clairvoire: Это одна из вещей, которая всегда работала на практике, хотя и была запрещена формально. Я не ожидаю, что какие-либо авторы компилятора должны были «добавлять» поддержку.

4. Это никогда не было «запрещено», это было просто «неопределенное поведение». Просто фактическое поведение в каждом компиляторе, о котором я знаю, должно было работать так, как вы ожидаете. C 11 просто в основном определял поведение, которое в любом случае использовал каждый компилятор.

Ответ №2:

Я бы обернул это преобразование в класс (чтобы, если вам нужно сменить платформу или что-то в этом роде, оно было хотя бы локализовано в одном месте), но да, это должно быть возможно.

Вы захотите использовать reinterpret_cast , не static_cast так хорошо.

Комментарии:

1. Вы правы, моя ошибка! Исправлен вопрос

Ответ №3:

Теоретически это неопределенное поведение. Однако это может работать в определенных системах / платформах.

Я бы посоветовал вам попытаться объединить 2 класса в 1. т.е.

 class Lib_Vertex{
// data (which is exactly same for both classes)
public:
// methods for LibA_Vertex
// methods for LibB_Vertex
};
  

Добавление методов в a class не повлияет на его размер. Возможно, вам придется немного изменить свой дизайн, но оно того стоит.

Комментарии:

1. Обычно я бы тоже использовал что-то подобное. Мне все равно пришлось бы делать копии массивов. Одна библиотека (2d physics lib) возвращает целые массивы вершин, используя свой внутренний класс вершин, который затем мне нужно передать в другую библиотеку (2d rendering lib), которая принимает массивы своего внутреннего класса вершин.

Ответ №4:

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