Вопрос

Для системы мне нужно преобразовать указатель в тип 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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top