Моя программа должна использовать пусто* для переноса данных или объектов в динамической ситуации с вызовом, так, чтобы это могло сослаться на данные произвольных типов, даже типов примитивов. Однако я недавно обнаружил, что процесс вниз бросающих, которые они освобождают* в случае классов с несколькими сбоями базовых классов и даже разрушают мою программу после вызова методов на них вниз литые указатели, даже если адреса памяти, кажется, корректны. Катастрофический отказ происходит во время доступа к "vtable".
Таким образом, я создал небольшой тестовый сценарий, среда является gcc 4.2 на Mac OS X:
class Shape {
public:
virtual int w() = 0;
virtual int h() = 0;
};
class Square : public Shape {
public:
int l;
int w() {return l;}
int h() {return l;}
};
class Decorated {
public:
int padding;
int w() {return 2*padding;}
int h() {return 2*padding;}
};
class DecoratedSquare : public Square, public Decorated {
public:
int w() {return Square::w() + Decorated::w();}
int h() {return Square::h() + Decorated::h();}
};
#include <iostream>
template <class T> T shape_cast(void *vp) {
// return dynamic_cast<T>(vp); // not possible, no pointer to class type
// return static_cast<T>(vp);
// return T(vp);
// return (T)vp;
return reinterpret_cast<T>(vp);
}
int main(int argc, char *argv[]) {
DecoratedSquare *ds = new DecoratedSquare;
ds->l = 20;
ds->padding = 5;
void *dsvp = ds;
std::cout << "Decorated (direct)" << ds->w() << "," << ds->h() << std::endl;
std::cout << "Shape " << shape_cast<Shape*>(dsvp)->w() << "," << shape_cast<Shape*>(dsvp)->h() << std::endl;
std::cout << "Square " << shape_cast<Square*>(dsvp)->w() << "," << shape_cast<Square*>(dsvp)->h() << std::endl;
std::cout << "Decorated (per void*) " << shape_cast<Decorated*>(dsvp)->w() << "," << shape_cast<Decorated*>(dsvp)->h() << std::endl;
std::cout << "DecoratedSquare " << shape_cast<DecoratedSquare*>(dsvp)->w() << "," << shape_cast<DecoratedSquare*>(dsvp)->h() << std::endl;
}
производит следующий вывод:
Decorated (direct)30,30
Shape 30,30
Square 30,30
Decorated (per void*) 73952,73952
DecoratedSquare 30,30
Как Вы видите, "Украшенный (на пустоту*)" результат является абсолютно неправильным. Это должно также быть 30,30 как в первой строке.
Безотносительно метода броска я использую в shape_cast (), я буду всегда получать те же неожиданные результаты для Украшенной части. Что-то абсолютно неправильно с ними, освобождают *.
От моего понимания C++ это должно на самом деле работать. Там шанс состоит в том, чтобы заставить это работать с пустотой*? Это может быть ошибкой в gcc?
Спасибо
Это не ошибка компилятора - это то, что делает reinterpret_cast
. Объект DecoratedSquare
будет размещен в памяти примерно так:
Square
Decorated
DecoratedSquare specific stuff
Преобразование указателя на него в void *
даст адрес начала этих данных без знания какого типа есть. reinterpret_cast
примет этот адрес и интерпретирует все, что там есть, как Decorated
, но фактическое содержимое памяти - это Square
. Это неправильно, поэтому вы получаете неопределенное поведение.
Вы должны получить правильные результаты, если вы reinterpret_cast
к правильному динамическому типу (то есть DecoratedSquare
), а затем конвертируете в базовый класс.
Повторите десять раз - единственное, что вы можете безопасно сделать с указателем reinterpret_cast
, - это reinterpret_cast
, чтобы он вернулся к тот же тип указателя, от которого он произошел. То же самое относится к преобразованию в void *
: вы должны преобразовать обратно в исходный тип.
Итак, если вы преобразовали DecoratedSquare *
в void *
, вы должны преобразовать его обратно в DecoratedSquare *
. Не Украшенный *
, не Квадрат *
, а не Форма *
. Некоторые из них могут работать на вашем компьютере, но это сочетание удачи и поведения, зависящего от реализации. Обычно он работает с одиночным наследованием, потому что нет очевидных причин для реализации указателей объектов таким образом, чтобы они перестали работать, но это не гарантируется, и в целом он не может работать для множественного наследования.
Вы говорите, что ваш код обращается к «произвольным типам, включая примитивные типы» через void *. В этом нет ничего плохого - по-видимому, тот, кто получает данные, знает, что следует рассматривать их как DecoratedSquare *
, а не как, скажем, int *
.
Если тот, кто его получает, знает только, что следует рассматривать его как базовый класс, например Decorated *
, то тот, кто преобразует его в void *
, должен static_cast
это сначала в базовый класс, затем в void *
:
void *decorated_vp = static_cast<Decorated*>(ds);
Теперь, когда вы приведете Decorated_vp
обратно в Decorated *
, вы получите результат static_cast
, что вам и нужно.
Статический_каст или динамический_каст при наличии множественного наследования может изменить представление указателя, сместив его так, чтобы он обозначал правильный адрес. Статический_каст определяет правильное смещение, учитывая информацию о статической типизации, а динамический_каст делает это, проверяя динамический тип. Если вы бросаете void*, вы теряете всю информацию о статическом типе и возможность получить информацию о динамическом типе, поэтому используемый вами reinterpret_cast предполагает, что смещение равно null, что иногда приводит к ошибке.