Проблемы с приведением void ** к T ** - PullRequest
2 голосов
/ 04 августа 2011

TL; DR: у меня есть производное **, которое я храню в Lua как пользовательские данные void *.Затем я пытаюсь вернуть его как базу ** и все ломается.Могу ли я что-нибудь сделать или это все безумие обречено на провал?

Подробности:

Я передаю некоторые данные между Lua и C ++, и Lua требует использованияvoid * для хранения пользовательских данных (то, что я использую Lua, не слишком важно, кроме того, что оно использует указатели void).Имеет смысл до сих пор.Допустим, у меня есть три класса, Base и Derived, с Derived, унаследованными от Base.Пользовательские данные, которые я передаю Lua, - это указатель на указатель, например:

template <typename T>
void lua_push(L, T* obj) {
    T** ud = (T**)lua_newuserdata(L, sizeof(T*)); // Create a new userdata
    *ud = obj; // Set a pointer to my object
    // rest of the function setting up other stuff omitted
}

Конечно, это хорошая шаблонная функция, поэтому я могу передать любой из трех своих типов таким образом.Позже я могу использовать другую шаблонную функцию для извлечения своих пользовательских данных из Lua, например так:

template <typename T>
T* lua_to(lua_State* L, int index) { 
    // there's normally a special metatable check here that ensures that 
    // this is the type I want, I've omitted it for this example
    return *(T**)lua_touserdata(L, index);
}

Это прекрасно работает, когда я передаю и выводлю тот же тип.Я столкнулся с проблемой, хотя при попытке вытащить Derived как Base.

В моем конкретном случае вектор хранится в Base.Я использую lua_push<Derived>(L, obj);, чтобы подтолкнуть мой объект к Луа.Позже, в другом месте, я вытаскиваю его, используя Base* obj = lua_to<Base>(L, i);.Затем я push_back некоторые вещи в моем векторе.Позже, другая часть кода извлекает тот же самый объект (проверенный сравнением указателей), за исключением того, что на этот раз объект Derived* obj = lua_to<Derived>(L, i); My Derived не видит объект, который был вставлен. Я считаю, что я сузил этоиз-за неправильного приведения, и я, вероятно, где-то повреждаю память, когда я звоню push_back

Итак, мой вопрос, есть ли способ заставить этот приём работать правильно?Я пробовал разные вкусы слепков.static_cast, dynamic_cast и reinterpret_cast не работают, либо дают мне один и тот же неправильный ответ, либо вообще не компилируют.

Конкретный пример:

Base* b = lua_to<Base>(L, -1); // Both lua_to's looking at the same object
Derived* d = lua_to<Derived>(L, -1); // You can be double sure because the pointers in the output match
std::cout << "Base: " << b << " " << b->myVec.size() << std::endl;
std::cout << "Derived: " << d << " " << d->myVec.size() << std::endl;

Вывод:

Base: 0xa1fb470 1
Derived: 0xa1fb470 0

Ответы [ 3 ]

3 голосов
/ 04 августа 2011

Код не является безопасным.Когда вы разыгрываете Base * на void *, вы всегда должны сначала разыграть void * обратно на Base *, а , а затем снова на 65 * *.Вот так:

Derived *obj = ...;
Base** ud = reinterpret_cast<Base **>(lua_newuserdata(L, sizeof(Base*)));
*ud = obj; // implicit cast Derived -> Base
...
Derived *obj = static_cast<Derived *>(*ud); // explicit Base -> Derived

По сути,

Y -> X -> void* -> X -> Y (safe)
Y -> X -> void* -> Y (unsafe)

Причина этого заключается в том, что фактическое значение указателя двух указателей, указывающих на один и тот же объект, может отличаться, если два указателя имеютРазличные типы.Работает ли это, зависит от различных факторов, таких как наследование и виртуальные функции.(Это всегда работает в C, так как C не имеет этих возможностей.)

2 голосов
/ 04 августа 2011

Это все зависит от используемого вами компилятора, но обычно указатель на базовый класс совпадает с указателем на производный класс. Выполнение принудительного броска не должно повредить ничему. Единственным исключением является случай множественного наследования; указатель на один базовый класс не будет таким же, как указатель на другой базовый класс, даже с тем же объектом. Компилятору необходимо знать точный тип исходного указателя, чтобы правильно настроить его, и приведение к void * теряет эту информацию.

1 голос
/ 04 августа 2011

В дополнение к ответу Марка Рэнсома вы также можете получить проблемы с этим типом приведения, если Derived содержит виртуальную функцию, а Base - нет, но, опять же, это зависит от компилятора.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...