Размещение нового и наследства

Добрый вечер всем.
Фрагмент кода будет стоить тысячи слов:

// Storage suitable for any of the listed instances
alignas(MaxAlign<Base, Derived1, Derived2>::value)
char storage[MaxSize<Base, Derived1, Derived2>::value];

// Instanciate one of the derived classes using placement new
new (storage) Derived2(3.14);// Later...

// Recover a pointer to the base class
Base &ref = *reinterpret_cast<Base*> (storage);

// Use its (virtual) functions
ref.print();

// Destroy it when we're done.
ref.~Base();

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

Поскольку размещение нового никогда не корректирует целевой указатель, это сводится к использованию reinterpret_cast upcast для базового класса. Теперь я знаю, что это опасно, так как более уместно static_cast корректирует указатель в некоторых случаях. [1]

И действительно, это вызывает неопределенное поведение. Вы найдете полный код здесь на Колиру (g ++ 4.9.0), где он быстро падает во время выполнения. Между тем на моем ПК (g ++ 4.8.2) все нормально. Обратите внимание, что в g ++ 4.9 вывод обоих указателей перед вызовом функций показывает идентичные значения … и работает.

Поэтому я попытался решить проблему в обратном направлении: подтолкнуть производный экземпляр так, чтобы указатель на Base будет равен storage,

void *ptr = static_cast<Derived2*>(reinterpret_cast<Base*>(storage));
new (ptr) Derived2(3.14);

Неудачно. Эта штука по-прежнему работает нормально на g ++ 4.8 и загорается на g ++ 4.9.

Изменить: если подумать, выше не так умно. Потому что, что если производный экземпляр заканчивается до storage… Ой.

Итак, мой вопрос: есть ли решение того, чего я пытаюсь достичь? Или же случаи, упомянутые в [1], достаточно четко определены, чтобы я мог написать код, который будет работать с подмножеством классов (например, без виртуального наследования)?

8

Решение

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

Coliru ссылка: http://coliru.stacked-crooked.com/a/dd9a633511a3d08d

#include <iostream>

struct Base {
virtual void print() {}
};

int main(int, char**) {
Base storage[1];
storage[0].print();

std::cout <<"Succeed";
return 0;
}

Достаточные условия для запуска этой ошибки:

  1. переменная «хранилище» размещается в стеке как массив

  2. print () должен быть виртуальным методом

  3. опция компилятора должна быть -O2

Если вы используете -O1, программа компилируется и запускается без проблем.

Кроме того, эта ошибка, кажется, появляется только при компиляции с g ++ 4.9.0.
Он работает нормально, если скомпилирован с VS2012 / 2013 или g ++ 4.7.2 (вы можете проверить это на http://www.compileonline.com/compile_cpp_online.php)

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

Замечания:
Фактический результат программы, данный в этом ответе, отличается от ОП. Это не показывает Ошибка сегментации. Однако при успешном запуске он должен вывести «Succeed», который не отображается при запуске на Coliru.

Редактировать:
Изменен код, чтобы повторить ошибку. Не требуется производного класса.

3

Другие решения


По вопросам рекламы [email protected]