Безопасная проверка типа переменной
-
10-07-2019 - |
Вопрос
Для системы мне нужно преобразовать указатель в тип long, а затем обратно в тип указателя.Как вы можете догадаться, это очень небезопасно.Я хотел использовать динамический_cast для преобразования, поэтому, если я их смешаю, я получу нулевой указатель.На этой странице написано http://publib.boulder.ibm.com/infocenter/lnxpcomp/v7v91/index.jsp?topic=/com.ibm.vacpp7l.doc/language/ref/clrc05keyword_dynamic_cast.htm
Оператор Dynamic_cast выполняет конверсию типа во время выполнения.Оператор Dynamic_CASC гарантирует преобразование указателя в базовый класс в указатель на полученный класс или преобразование LVALUE, ссылаясь на базовый класс на ссылку на полученный класс.Таким образом, программа может безопасно использовать классовую иерархию.Этот оператор и оператор TypeID предоставляют поддержку информации о типе выполнения (RTTI) в C ++.
и я хотел бы получить ошибку, если она равна нулю, поэтому я написал свой собственный динамический приведение
template<class T, class T2> T mydynamic_cast(T2 p)
{
assert(dynamic_cast<T>(p));
return reinterpret_cast<T>(p);
}
При использовании MSVC я получаю сообщение об ошибке «ошибка C2681:'длинный' :недопустимый тип выражения для динамического_cast».Оказывается, это будет работать только с классами, имеющими виртуальные функции...Что за черт!Я знаю, что смысл динамического приведения заключался в проблеме наследования приведения вверх/вниз, но я также думал, что это динамическое решение проблемы приведения типов.Я знаю, что мог бы использовать reinterpret_cast, но это не гарантирует такого же уровня безопасности.
Что мне следует использовать, чтобы проверить, является ли мой тип одного и того же типа?Я мог бы сравнить два typeid, но у меня возникла бы проблема, если бы я захотел привести производный тип к его базе.Итак, как я могу это решить?
Решение
Мне приходилось делать подобные вещи при загрузке C ++ DLL в приложениях, написанных на языках, которые поддерживают только интерфейс C. Вот решение, которое даст вам немедленную ошибку, если был передан неожиданный тип объекта. Это может значительно упростить диагностику, если что-то идет не так.
Хитрость в том, что каждый класс, который вы выделяете в качестве дескриптора, должен наследоваться от общего базового класса. Р>
#include <stdexcept>
#include <typeinfo>
#include <string>
#include <iostream>
using namespace std;
// Any class that needs to be passed out as a handle must inherit from this class.
// Use virtual inheritance if needed in multiple inheritance situations.
class Base
{
public:
virtual ~Base() {} // Ensure a v-table exists for RTTI/dynamic_cast to work
};
class ClassA : public Base
{
};
class ClassB : public Base
{
};
class ClassC
{
public:
virtual ~ClassC() {}
};
// Convert a pointer to a long handle. Always use this function
// to pass handles to outside code. It ensures that T does derive
// from Base, and that things work properly in a multiple inheritance
// situation.
template <typename T>
long pointer_to_handle_cast(T ptr)
{
return reinterpret_cast<long>(static_cast<Base*>(ptr));
}
// Convert a long handle back to a pointer. This makes sure at
// compile time that T does derive from Base. Throws an exception
// if handle is NULL, or a pointer to a non-rtti object, or a pointer
// to a class not convertable to T.
template <typename T>
T safe_handle_cast(long handle)
{
if (handle == NULL)
throw invalid_argument(string("Error casting null pointer to ") + (typeid(T).name()));
Base *base = static_cast<T>(NULL); // Check at compile time that T converts to a Base *
base = reinterpret_cast<Base *>(handle);
T result = NULL;
try
{
result = dynamic_cast<T>(base);
}
catch(__non_rtti_object &)
{
throw invalid_argument(string("Error casting non-rtti object to ") + (typeid(T).name()));
}
if (!result)
throw invalid_argument(string("Error casting pointer to ") + typeid(*base).name() + " to " + (typeid(T).name()));
return result;
}
int main()
{
ClassA *a = new ClassA();
ClassB *b = new ClassB();
ClassC *c = new ClassC();
long d = 0;
long ahandle = pointer_to_handle_cast(a);
long bhandle = pointer_to_handle_cast(b);
// long chandle = pointer_to_handle_cast(c); //Won't compile
long chandle = reinterpret_cast<long>(c);
// long dhandle = pointer_to_handle_cast(&d); Won't compile
long dhandle = reinterpret_cast<long>(&d);
// send handle to library
//...
// get handle back
try
{
a = safe_handle_cast<ClassA *>(ahandle);
//a = safe_handle_cast<ClassA *>(bhandle); // fails at runtime
//a = safe_handle_cast<ClassA *>(chandle); // fails at runtime
//a = safe_handle_cast<ClassA *>(dhandle); // fails at runtime
//a = safe_handle_cast<ClassA *>(NULL); // fails at runtime
//c = safe_handle_cast<ClassC *>(chandle); // Won't compile
}
catch (invalid_argument &ex)
{
cout << ex.what() << endl;
}
return 0;
}
Другие советы
dynamic_cast
может использоваться только между классами, связанными через наследование. Для преобразования указателя в long или наоборот вы можете использовать reinterpret_cast
. Чтобы проверить, является ли указатель нулевым, вы можете assert(ptr != 0)
. Однако обычно не рекомендуется использовать <=>. Зачем вам нужно конвертировать указатель на long?
Другой вариант - использовать объединение.
union U {
int* i_ptr_;
long l;
}
Опять же, союз тоже нужен очень редко. Р>
Помните, что в Windows 64 указатель будет 64-разрядным, но long
все равно будет 32-разрядным, и ваш код будет поврежден. По крайней мере, вам нужно сделать выбор целочисленного типа в зависимости от платформы. Я не знаю, есть ли в MSVC поддержка uintptr_t
, типа, предоставленного в C99 для хранения указателей; это будет лучший тип для использования, если он доступен.
В остальном, другие достаточно подробно рассмотрели причины и причины dynamic_cast
и reinterpret_cast
.
reinterpret_cast - правильное приведение для использования здесь.
Это в значительной степени единственная вещь, которую он может сделать безопасно.
reinterpret_cast от типа указателя к типу T и обратно к исходному типу указателя дает исходный указатель. (Предполагая, что T является указателем или целочисленным типом, который по крайней мере такой же большой, как и исходный тип указателя)
Обратите внимание, что reinterpret_cast от типа указателя до T не указан. Нет никаких гарантий относительно значения типа T, кроме , что если вы затем повторно интерпретируете его обратно в исходный тип, вы получите исходное значение. Поэтому, если вы не пытаетесь что-либо сделать с промежуточным длинным значением в вашем случае, reinterpret_cast абсолютно безопасен и переносим.
Редактировать: Конечно, это не поможет, если вы не знаете во втором акте, каким был исходный тип. В этом случае вы облажались. Long не может каким-либо образом нести информацию о типе, из которого указатель был преобразован. Р>
Вы можете использовать reinterpret_cast
для приведения к целочисленному типу и обратно к типу указателя. Если целочисленный тип достаточно велик для хранения значения указателя, то это преобразование не изменит значение указателя.
Как уже говорят другие, не определено поведение при использовании dynamic_cast для неполиморфного класса (кроме случаев, когда вы выполняете upcast, который в любом случае неявен и игнорируется здесь), и он также работает только с указателями или ссылками. Не на целочисленных типах. Р>
Лучше использовать ::intptr_t
в различных системах posix. Вы можете использовать этот тип в качестве промежуточного типа, который вы используете. Р>
Что касается проверки успешности конверсии, вы можете использовать sizeof:
BOOST_STATIC_ASSERT(sizeof(T1) >= sizeof(T2));
потерпит неудачу во время компиляции, если преобразование не может быть сделано. Или продолжайте использовать assert с этим условием, и оно будет утверждаться во время выполнения.
Предупреждение. Это не помешает вам привести T*
к intptr_t
обратно в U*
с типом U, отличным от T. Таким образом, это только гарантирует, что приведение не будет измените значение указателя, если вы приведете значение от <=> к <=> и обратно к <=>. (Спасибо Никола, указывая, что вы можете ожидать другую защиту).
То, что вы хотите сделать, звучит как очень плохая и опасная идея, но если вы ДОЛЖНЫ сделать это (т.е. вы работаете в устаревшей системе или на оборудовании, которое, как вы знаете, никогда не изменится), то я бы предложил обернуть указатель в некоторой простой структуре, которая содержит два члена: 1) указатель void на экземпляр вашего объекта и строку, перечисление или какой-либо другой вид уникального идентификатора, который скажет вам, к чему следует привести исходный void *. Вот пример того, что я имел в виду (примечание: я не удосужился проверить это, поэтому в нем могут быть синтаксические ошибки):
struct PtrWrapper {
void* m_theRealPointer;
std::string m_type;
};
void YourDangerousMethod( long argument ) {
if ( !argument )
return;
PtrWrapper& pw = *(PtrWrapper*)argument;
assert( !pw.m_type.empty() );
if ( pw.m_type == "ClassA" ) {
ClassA* a = (ClassA*)pw.m_theRealPointer;
a->DoSomething();
} else if (...) { ... }
}
dynamic_cast<>
это приведение, предназначенное для использования только на кабриолет типы (в полиморфном смысле).Принуждение к актерскому составу pointer
к long
(litb правильно предлагает static_assert для обеспечения совместимости размеров) вся информация о тип указателя потеряны.Невозможно реализовать safe_reinterpret_cast<>
чтобы получить указатель обратно:как значение, так и тип.
Чтобы уточнить, что я имею в виду:
struct a_kind {};
struct b_kind {};
void function(long ptr)
{}
int
main(int argc, char *argv[])
{
a_kind * ptr1 = new a_kind;
b_kind * ptr2 = new b_kind;
function( (long)ptr1 );
function( (long)ptr2 );
return 0;
}
Нет способа function()
чтобы определить тип переданного указателя и «вниз» привести его к правильному типу, если только:
- long обернут объектом с некоторой информацией типа.
- сам тип закодирован в объекте, на который ссылается.
Оба решения уродливы, и их следует избегать, поскольку они являются суррогатами RTTI.
также лучше использовать size_t вместо long - я думаю, что этот тип гарантированно совместим с размером адресного пространства.
Как только вы решили навести указатель на длинную, вы бросили тип безопасности на ветер.
dynamic_cast используется для приведения в действие & amp; вниз по дереву деривации. То есть от указателя на базовый класс до указателя на производный класс. Если у вас есть:
class Base
{
};
class Foo : public Base
{
};
class Bar : public Base
{
};
Вы можете использовать dynamic_cast таким образом ...
Base* obj = new Bar;
Bar* bar = dynamic_cast<Bar*>(obj); // this returns a pointer to the derived type because obj actually is a 'Bar' object
assert( bar != 0 );
Foo* foo = dynamic_cast<Foo*>(obj); // this returns NULL because obj isn't a Foo
assert( foo == 0 );
... но вы не можете использовать динамическое приведение для преобразования в дерево деривации. Для этого вам понадобится reinterpret_cast или C-style.