Безопасная проверка типа переменной - PullRequest
1 голос
/ 22 ноября 2008

Для системы мне нужно преобразовать указатель на тип long, а затем long обратно в тип указателя. Как вы можете догадаться, это очень небезопасно. То, что я хотел сделать, это использовать dynamic_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_cast гарантирует преобразование указателя в базу класс для указателя на производный класс, или преобразование 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:« длинный »: недопустимый тип выражения для dynamic_cast». Оказывается, это будет работать только с классами, которые имеют виртуальные функции ... WTF! Я знаю, что смысл динамического приведения был в проблеме наследования приведения вверх / вниз, но я также думал, что это должно было решить проблему приведения типов динамически. Я знаю, что могу использовать reinterpret_cast, но это не гарантирует такой же тип безопасности.

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

Ответы [ 9 ]

2 голосов
/ 22 ноября 2008

dynamic_cast может использоваться только между классами, связанными через наследование. Для преобразования указателя в long или наоборот вы можете использовать reinterpret_cast. Чтобы проверить, является ли указатель нулевым, вы можете assert(ptr != 0). Однако обычно не рекомендуется использовать reinterpret_cast. Зачем вам нужно конвертировать указатель на long?

Другой вариант - использовать объединение:

union  U { 
int* i_ptr_;
long l;
}

Опять же, союз тоже нужен очень редко.

1 голос
/ 28 ноября 2008

Мне приходилось делать подобные вещи при загрузке 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;
}
1 голос
/ 22 ноября 2008

Помните, что в Windows 64 указатель будет 64-разрядной величиной, но long все равно будет 32-разрядной величиной, и ваш код будет поврежден. По крайней мере, вам нужно сделать выбор целочисленного типа в зависимости от платформы. Я не знаю, имеет ли MSVC поддержку uintptr_t, типа, предоставленного в C99 для хранения указателей; это будет лучший тип для использования, если он доступен.

Что касается остальных, то другие достаточно подробно рассмотрели причины и причины, по которым dynamic_cast против reinterpret_cast.

0 голосов
/ 28 декабря 2008

также лучше использовать size_t вместо long - я думаю, что этот тип гарантированно совместим с размером адресного пространства.

0 голосов
/ 22 ноября 2008

То, что вы хотите сделать, звучит как очень плохая и опасная идея, но если вы ДОЛЖНЫ сделать это (т.е. вы работаете в устаревшей системе или на оборудовании, которое, как вы знаете, никогда не изменится), то я бы предложил обернуть указатель в некоторой простой структуре, которая содержит два члена: 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 (...) { ... }

}
0 голосов
/ 22 ноября 2008

Как только вы решили навести указатель на длинную, вы бросили тип безопасности на ветер.

dynamic_cast используется для приведения вверх и вниз по дереву деривации. То есть от указателя на базовый класс до указателя на производный класс. Если у вас есть:

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.

0 голосов
/ 22 ноября 2008

reinterpret_cast - правильное приведение для использования здесь.

Это в значительной степени только вещь, которую он может сделать безопасно.

reinterpret_cast от типа указателя к типу T и обратно к исходному типу указателя дает исходный указатель. (Предполагая, что T является указателем или целочисленным типом, который по крайней мере такой же большой, как и исходный тип указателя)

Обратите внимание, что reinterpret_cast от типа указателя до T не указан. Нет никаких гарантий относительно значения типа T, , за исключением , что если вы затем повторно интерпретируете его обратно в исходный тип, вы получите исходное значение. Поэтому, если вы не пытаетесь что-либо сделать с промежуточным длинным значением в вашем случае, reinterpret_cast абсолютно безопасен и переносим.

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

0 голосов
/ 22 ноября 2008

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

Как уже говорят другие, не определено поведение для использования dynamic_cast с неполиморфным классом (кроме случаев, когда вы выполняете upcast, который в любом случае неявен и игнорируется здесь), и он также работает только с указателями или ссылками. Не на целочисленных типах.

Вам лучше использовать ::intptr_t в различных системах posix. Вы можете использовать этот тип в качестве промежуточного типа, который вы используете.

Что касается проверки успешности конвертации, вы можете использовать sizeof:

BOOST_STATIC_ASSERT(sizeof(T1) >= sizeof(T2));

потерпит неудачу во время компиляции, если преобразование не может быть выполнено. Или продолжайте использовать assert с этим условием, и оно будет утверждаться во время выполнения.

Предупреждение: Это не помешает вам привести T* к intptr_t обратно к U* с U другого типа, чем T. Таким образом, это только гарантирует, что приведение не изменится значение указателя, если вы приведете от T* к intptr_t и обратно к T*. (Спасибо Никола, указывая, что вы можете ожидать другую защиту).

0 голосов
/ 22 ноября 2008

dynamic_cast<> - это приведение, предназначенное для использования только на конвертируемых типах (в полиморфном смысле). При принудительном приведении pointer к long (горит правильно, что 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() нет способа определить, какой тип указателя передан, и "down" приведёт его к нужному типу, кроме случаев, когда:

  • long обернут объектом с некоторой информацией типа.
  • сам тип закодирован в ссылочном объекте.

Оба решения ужасны и их следует избегать, поскольку они являются суррогатами RTTI.

...