Domanda

Per un sistema ho bisogno di convertire un puntatore in un lungo e poi il lungo in un tipo di puntatore. Come puoi immaginare, questo non è sicuro. Quello che volevo fare è usare dynamic_cast per fare la conversione, quindi se li mescolassi otterrò un puntatore nullo. Questa pagina dice http://publib.boulder.ibm.com/infocenter/lnxpcomp/v7v91/index.jsp?topic=/com.ibm.vacpp7l.doc/language/ref/clrc05keyword_dynamic_cast.htm

  

L'operatore dynamic_cast esegue   digitare le conversioni in fase di esecuzione. Il   L'operatore dynamic_cast garantisce il   conversione di un puntatore in una base   classe a un puntatore a una classe derivata,   o la conversione di un valore   riferendosi a una classe base a   riferimento a una classe derivata. UN   il programma può quindi utilizzare una classe   gerarchia in sicurezza. Questo operatore e   l'operatore typeid fornisce il runtime   supporto per informazioni di tipo (RTTI) in   C ++.

e vorrei ricevere un errore se è nullo, quindi ho scritto il mio cast dinamico

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

Con MSVC ottengo l'errore " errore C2681: 'long': tipo di espressione non valido per dynamic_cast " ;. Si scopre che funzionerà solo con classi che hanno funzioni virtuali ... WTF! So che il punto di un cast dinamico era per il problema dell'ereditarietà del casting up / down, ma ho anche pensato che fosse risolvere il problema del cast di tipo in modo dinamico. So che potrei usare reinterpret_cast ma ciò non garantisce lo stesso tipo di sicurezza.

Cosa devo usare per verificare se il mio typecast è dello stesso tipo? Potrei confrontare i due caratteri tipografici ma avrei un problema quando voglio scrivere un derivato alla sua base. Quindi, come posso risolvere questo?

È stato utile?

Soluzione

Ho dovuto fare cose simili durante il caricamento di DLL C ++ in app scritte in lingue che supportano solo un'interfaccia C. Ecco una soluzione che ti darà un errore immediato se viene passato un tipo di oggetto imprevisto. Ciò può rendere le cose molto più facili da diagnosticare quando qualcosa va storto.

Il trucco è che ogni classe che passi come handle deve ereditare da una classe base comune.

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

Altri suggerimenti

dynamic_cast può essere utilizzato solo tra classi correlate tramite ereditarietà. Per convertire un puntatore in lungo o viceversa, puoi utilizzare reinterpret_cast. Per verificare se il puntatore è nullo, è possibile assert(ptr != 0). Tuttavia, di solito non è consigliabile utilizzare <=>. Perché è necessario convertire un puntatore a lungo?

Un'altra opzione è usare un'unione:

union  U { 
int* i_ptr_;
long l;
}

Anche in questo caso, anche l'unione è necessaria solo di rado.

Ricorda che in Windows 64, un puntatore sarà una quantità a 64 bit ma long sarà comunque una quantità a 32 bit e il tuo codice è rotto. Per lo meno, è necessario scegliere il tipo intero in base alla piattaforma. Non so se MSVC abbia il supporto per uintptr_t, il tipo fornito in C99 per contenere i puntatori; sarebbe il tipo migliore da utilizzare se è disponibile.

Per quanto riguarda il resto, altri hanno affrontato il perché e il perché di dynamic_cast vs reinterpret_cast in modo sufficiente.

reinterpret_cast è il cast corretto da utilizzare qui.

Questa è praticamente la unica cosa che può fare in sicurezza.

reinterpretare_cast da un tipo di puntatore a un tipo T e viceversa al tipo di puntatore originale restituisce il puntatore originale. (Supponendo che T sia un tipo di puntatore o intero che sia grande almeno quanto il tipo di puntatore originale)

Notare che reinterpret_cast da un tipo di puntatore a T non è specificato. Non ci sono garanzie circa il valore del tipo T, tranne che se poi lo reinterpretate e lo ritrasmettete al tipo originale, otterrete il valore originale. Quindi supponendo che non provi a fare nulla con il valore lungo intermedio nel tuo caso, reinterpret_cast è perfettamente sicuro e portatile.

Modifica: ovviamente questo non aiuta se non sai al secondo cast, quale fosse il tipo originale. In tal caso, sei fregato. Il long non può in alcun modo portare informazioni sul tipo da quale puntatore è stato convertito.

Puoi usare reinterpret_cast per trasmettere un tipo integrale e tornare al tipo di puntatore. Se il tipo integrale è abbastanza grande da memorizzare il valore del puntatore, tale conversione non cambierà il valore del puntatore.

Come già altri affermano, non è definito il comportamento utilizzare dynamic_cast su una classe non polimorfica (tranne quando si esegue un upcast, che è comunque implicito ed essere ignorato qui), e funziona anche solo su puntatori o riferimenti. Non sui tipi integrali.

È meglio usare ::intptr_t presente in vari sistemi posix. Puoi utilizzare quel tipo come tipo intermedio in cui esegui il cast.

Per quanto riguarda il controllo della riuscita della conversione, puoi utilizzare sizeof:

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

fallirà al momento della compilazione se la conversione non può essere eseguita. Oppure continua a utilizzare assert con tale condizione, che verrà invece affermato in fase di esecuzione.

Avviso: questo non ti impedirà di trasmettere T* a intptr_t di nuovo a U* con U un tipo diverso da T. Pertanto, questo ti garantisce solo che il cast non lo farà modificare il valore del puntatore se si esegue il cast da <=> a <=> e si torna a <=>. (Grazie alla segnalazione di Nicola potresti aspettarti un'altra protezione).

Quello che vuoi fare sembra un'idea davvero cattiva e pericolosa, ma se DEVI farlo (cioè stai lavorando in un sistema legacy o su un hardware che sai che non cambierà mai), allora suggerirei di avvolgere il puntatore in una sorta di semplice struttura che contiene due membri: 1) un puntatore vuoto all'istanza dell'oggetto e una stringa, un enum o un altro tipo di identificatore univoco che ti dirà a cosa lanciare il vuoto originale *. Ecco un esempio di ciò che intendevo (nota: non mi sono preoccupato di testarlo, quindi potrebbero esserci errori sintattici):

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<> è un cast destinato ad essere utilizzato solo su tipi convertibili (in senso polimorfico). Forzare il cast di un pointer in un long (litb suggerisce correttamente static_assert per garantire la compatibilità delle dimensioni) tutte le informazioni sul tipo di puntatore vanno perse. Non è possibile implementare un safe_reinterpret_cast<> per ottenere il puntatore indietro: sia valore che tipo.

Per chiarire cosa intendo:

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

Non è possibile per function() determinare il tipo di puntatore passato e " down " esegui il cast nel tipo corretto, a meno che:

  • il lungo è racchiuso da un oggetto con alcune informazioni del tipo.
  • il tipo stesso è codificato nell'oggetto referenziato.

Entrambe le soluzioni sono brutte e dovrebbero essere evitate, poiché sono surrogati di RTTI.

inoltre, utilizzare meglio size_t anziché un lungo - penso che questo tipo sia garantito per essere compatibile con la dimensione dello spazio degli indirizzi.

Non appena hai deciso di lanciare un puntatore a lungo, hai lanciato il tipo di sicurezza sul vento.

dynamic_cast è usato per lanciare & amp; giù per un albero di derivazione. Cioè, da un puntatore di classe base a un puntatore di classe derivato. Se hai:

class Base
{
};

class Foo : public  Base
{
};

class Bar : public Base
{
};

Puoi usare dynamic_cast in questo modo ...

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

... ma non puoi usare il cast dinamico per eseguire il cast in un albero fuori di derivazione. Per questo è necessario reinterpret_cast o cast in stile C.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top