Добрый вечер всем.
Фрагмент кода будет стоить тысячи слов:
// 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], достаточно четко определены, чтобы я мог написать код, который будет работать с подмножеством классов (например, без виртуального наследования)?
Я только что немного изменил ваш код, похоже, что 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;
}
Достаточные условия для запуска этой ошибки:
переменная «хранилище» размещается в стеке как массив
print () должен быть виртуальным методом
опция компилятора должна быть -O2
Если вы используете -O1, программа компилируется и запускается без проблем.
Кроме того, эта ошибка, кажется, появляется только при компиляции с g ++ 4.9.0.
Он работает нормально, если скомпилирован с VS2012 / 2013 или g ++ 4.7.2 (вы можете проверить это на http://www.compileonline.com/compile_cpp_online.php)
Судя по вышесказанному, я думаю, что это может быть специфическая проблема компилятора.
Замечания:
Фактический результат программы, данный в этом ответе, отличается от ОП. Это не показывает Ошибка сегментации. Однако при успешном запуске он должен вывести «Succeed», который не отображается при запуске на Coliru.
Редактировать:
Изменен код, чтобы повторить ошибку. Не требуется производного класса.