Question

Pour un système, je dois convertir un pointeur en type long, puis long en type de pointeur. Comme vous pouvez le deviner, cela est très dangereux. Ce que je voulais faire, c'est utiliser dynamic_cast pour effectuer la conversion, donc si je les mélangeais, j'obtiendrais un pointeur nul. Cette page dit http://publib.boulder.ibm.com/infocenter/lnxpcomp/v7v91/index.jsp?topic=/com.ibm.vacpp7l.doc/language/ref/clrc05keyword_dynamic_cast.htm

  

L'opérateur dynamic_cast effectue   tapez les conversions au moment de l'exécution. le   L'opérateur dynamic_cast garantit la   conversion d'un pointeur en base   class à un pointeur sur une classe dérivée,   ou la conversion d'une valeur   se référant à une classe de base à un   référence à une classe dérivée. UNE   programme peut ainsi utiliser une classe   hiérarchie en toute sécurité. Cet opérateur et   l'opérateur typeid fournit le temps d'exécution   prise en charge des informations de type (RTTI) dans   C ++.

et je voudrais obtenir une erreur si elle est nulle alors j'ai écrit ma propre distribution dynamique

template<class T, class T2> T mydynamic_cast(T2 p)
{
    assert(dynamic_cast<T>(p));
    return reinterpret_cast<T>(p);
}

Avec MSVC, l'erreur & "; erreur C2681: 'long': type d'expression non valide pour dynamic_cast &"; Il s'avère que cela ne fonctionnera qu'avec les classes qui ont des fonctions virtuelles ... WTF! Je sais que l’intérêt d’une distribution dynamique concerne le problème d’héritage ascendant / descendant, mais j’ai aussi pensé que c’était pour résoudre le problème de la distribution typographique de manière dynamique. Je sais que je pourrais utiliser reinterpret_cast mais cela ne garantit pas le même type de sécurité.

Que dois-je utiliser pour vérifier si ma conversion de type est du même type? Je pourrais comparer les deux typesid mais j'aurais un problème lorsque je veux transtyper un dérivé à sa base. Alors, comment puis-je résoudre ce problème?

Était-ce utile?

La solution

J'ai eu à faire la même chose lors du chargement de DLL C ++ dans des applications écrites dans des langages ne prenant en charge qu'une interface C. Voici une solution qui vous donnera une erreur immédiate si un type d'objet inattendu a été passé. Cela peut rendre les choses beaucoup plus faciles à diagnostiquer quand quelque chose ne va pas.

Le truc, c’est que chaque classe que vous transmettez en tant que descripteur doit hériter d’une classe de base commune.

#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;
}

Autres conseils

dynamic_cast ne peut être utilisé qu'entre des classes liées par héritage. Pour convertir un pointeur en long ou vice-versa, vous pouvez utiliser reinterpret_cast. Pour vérifier si le pointeur est null, vous pouvez assert(ptr != 0). Cependant, il est généralement déconseillé d'utiliser <=>. Pourquoi avez-vous besoin de convertir un pointeur en long?

Une autre option consiste à utiliser une union:

union  U { 
int* i_ptr_;
long l;
}

Encore une fois, l’union n’est également nécessaire que rarement.

N'oubliez pas que dans Windows 64, un pointeur sera une quantité 64 bits, mais long sera toujours une quantité 32 bits et votre code sera cassé. À tout le moins, vous devez choisir le type de nombre entier en fonction de la plate-forme. Je ne sais pas si MSVC prend en charge uintptr_t, le type fourni dans C99 pour les pointeurs de maintien; ce serait le meilleur type à utiliser s'il est disponible.

Pour ce qui est du reste, d’autres ont suffisamment expliqué le vs dynamic_cast vs reinterpret_cast.

reinterpret_cast est la distribution correcte à utiliser ici.

C’est à peu près la seule chose qu’il peut faire en toute sécurité.

réinterpréter_cast d'un type de pointeur sur un type T et revenir au type de pointeur d'origine donne le pointeur d'origine. (En supposant que T soit un pointeur ou un type entier au moins aussi gros que le type de pointeur d'origine)

Notez que réinterpréter_cast d'un type de pointeur vers T n'est pas spécifié. Il n’existe aucune garantie quant à la valeur du type T, si ce n’est que, si vous le réinterprétez_de nouveau au type initial, vous obtenez la valeur initiale. Donc, en supposant que vous n'essayiez rien de faire avec la valeur intermédiaire longue dans votre cas, reinterpret_cast est parfaitement sûr et portable.

Edit: Bien sûr, cela n’aide en rien que vous ne connaissiez pas le type original lors de la deuxième distribution. Dans ce cas, vous êtes foutu. Le long ne peut en aucun cas porter des informations de type sur le pointeur à partir duquel il a été converti.

Vous pouvez utiliser reinterpret_cast pour transtyper vers un type intégral et revenir au type de pointeur. Si le type d'intégral est suffisamment grand pour stocker la valeur du pointeur, cette conversion ne modifiera pas la valeur du pointeur.

Comme d’autres le disent déjà, l’utilisation de dynamic_cast sur une classe non polymorphe n’est pas un comportement défini (sauf lorsque vous effectuez un upcast, qui est implicite et doit être ignoré ici), et ne fonctionne que sur les pointeurs ou les références. Pas sur les types intégraux.

Il vaut mieux utiliser ::intptr_t dans divers systèmes posix. Vous pouvez utiliser ce type comme type intermédiaire vers lequel vous lancez le casting.

En ce qui concerne votre vérification de la réussite de la conversion, vous pouvez utiliser sizeof:

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

échouera à la compilation si la conversion ne peut pas être effectuée. Ou continuez à utiliser assert avec cette condition, qui sera alors validée au moment de l'exécution.

Avertissement: Cela ne vous empêchera pas de convertir T* vers intptr_t en U* avec avec un autre type que T. Donc, cela garantit uniquement que la distribution ne le sera pas. remplacez la valeur du pointeur si vous effectuez une conversion de <=> à <=> puis à <=>. (Merci à Nicola, vous pouvez vous attendre à une autre protection).

Ce que vous voulez faire semble être une idée vraiment mauvaise et dangereuse, mais si vous DEVEZ le faire (c’est-à-dire que vous travaillez dans un système hérité ou sur du matériel dont vous savez qu'il ne changera jamais), je suggérerais alors de pointeur dans une sorte de structure simple qui contient deux membres: 1) un pointeur vide vers votre instance d'objet et une chaîne, enum ou tout autre type d'identificateur unique qui vous indiquera sur quoi lancer le vide original * vers. Voici un exemple de ce que je voulais dire (note: je ne me suis pas donné la peine de le tester, il peut donc y avoir des erreurs de syntaxe):

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<> est une conversion destinée à être utilisée uniquement sur les types convertibles (au sens polymorphe). Forcer la conversion d'un pointer vers un long (litb suggère correctement à static_assert de s'assurer de la compatibilité de la taille), toutes les informations relatives au type du pointeur sont perdues. Il n'y a aucun moyen d'implémenter un safe_reinterpret_cast<> pour récupérer le pointeur: valeur et type.

Pour clarifier ce que je veux dire:

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;
}

Il n’existe aucun moyen pour function() de déterminer le type de pointeur passé et & "down &"; lancez-le au type approprié, sauf si:

  • le long est encapsulé par un objet avec des informations du type.
  • le type lui-même est codé dans l'objet référencé.

Les deux solutions sont laides et devraient être évitées, car sont des substituts RTTI.

Aussi, mieux vaut utiliser size_t au lieu de long. Je pense que ce type est compatible avec la taille de l'espace d'adressage.

Dès que vous avez décidé de lancer un pointeur sur un long point, vous avez jeté la sécurité de type au vent.

dynamic_cast est utilisé pour lancer & amp; dans un arbre de dérivation. C'est-à-dire d'un pointeur de classe de base à un pointeur de classe dérivée. Si vous avez:

class Base
{
};

class Foo : public  Base
{
};

class Bar : public Base
{
};

Vous pouvez utiliser dynamic_cast de cette façon ...

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 );

... mais vous ne pouvez pas utiliser la conversion dynamique pour intégrer une arborescence de dérivation. Vous avez besoin de reinterpret_cast ou de castes de style C pour cela.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top