2016-05-26 65 views
5

Sto provando a lavorare su un progetto che richiederà di determinare un tipo di oggetto polimorfico in fase di esecuzione, in modo che possa eseguire il cast. Un esempio di ciò che intendo:C++ Determina il tipo di un oggetto polimorfico in fase di esecuzione

class A{ 
}; 
class B: public A{ 
    public: 
     void foo(){ 
      printf("B::foo()\n"); 
     } 
}; 

tardi, avrò un gruppo di oggetti B che sono essenzialmente memorizzati come tale:

std::vector<A*> v; 
v.push_back(new B()); 

E certamente necessario chiamare alcuni metodi di overload definite :

void bar(B* b){ 
    b->foo(); 
} 

Dopo passando oggetti memorizzati in v. Il problema che sto avendo è che nel mio attuale caso d'uso, non so il tipo di B a tempo di compilazione, quindi non posso chiamare bar dicendo bar((B*)v.get(0));

La soluzione che ho pensato Potrei aver bisogno di determinare in qualche modo il tipo che ogni oggetto è in fase di runtime, in modo che possa lanciarlo prima di passarlo a bar.

La soluzione che ho provato finora è stato quello di utilizzare decltype, ma non ha funzionato per me perché solo restituisce il tipo statico del valore passato, non il tipo in fase di esecuzione.

Inoltre, non voglio utilizzare librerie di terze parti per questo progetto, poiché mi piacerebbe renderlo il più piccolo possibile.

Grazie per il vostro aiuto.

Edit:

Io non credo di essere stato abbastanza chiaro in quello che intendevo quando ho descritto il mio problema. Nel mio caso d'uso reale (che è un po 'lungo per provare a postare qui, anche se posso postarne alcune parti se necessario), sto provando a scrivere una libreria in cui c'è una classe base (qui rappresentata come A) che può essere esteso dall'utente nelle proprie classi personalizzate (qui rappresentato come B). Nel mio esempio, si suppone che B::foo() rappresenti il ​​modo in cui ciascuna sottoclasse di A può avere i propri membri dati che vengono gestiti successivamente da un metodo (rappresentato nel mio esempio come bar). Questo è anche il motivo per cui A non può semplicemente avere un qualche metodo virtuale foo.

Il problema principale che sto avendo è, dal momento che B si suppone sia definito dall'utente, non so cosa sia in fase di compilazione, ma lo faccio al momento del collegamento (poiché il collegamento viene dopo). Questo è il motivo per cui l'uso di dynamic_cast (come suggerito da Sam e Remy) non funzionerà, perché da quello che ho capito, richiede che io conosca tutte le possibilità di ciò che potrebbe essere B, che io non conosco. Anche se sembrava molto vicino a quello che poteva funzionare per me. Se ci fosse un modo per ottenere tutte le sottoclassi possibili (ad esempio, con macro o modelli di preprocessore), penso che questo potrebbe essere in grado di funzionare.

Spero di averlo spiegato meglio questa volta. Grazie ancora per il tuo aiuto.

Edit 2: Un altro punto chiarifica: Nel progetto attuale sto lavorando, voglio solo che l'utente è tenuto a scrivere le proprie B classi e sovraccarico bar a lavorare con le loro classi personalizzate. Tuttavia, non desidero che all'utente venga richiesto di chiamare bar. Piuttosto, mi piacerebbe avere bar essere chiamato da una classe base (che io definisco).Questa è la ragione principale per cui provo a farlo anche nel modo in cui sono. Quello, e per vedere se posso.

La speranza che cancella tutto. Grazie ancora.

+0

Eventuali duplicati di [Perché abbiamo bisogno di metodi virtuali in C++?] (Http://stackoverflow.com/questions/2391679/why-do-we-need-virtual-methods-in-c) –

+0

Dalle tue modifiche, sembra che il modo corretto sia ancora membri 'virtuali' in' classe A', che forniscono pieno accesso ai membri dei dati per essere presenti in qualsiasi classe derivata. Tale accesso può includere informazioni sul tipo di runtime, ecc. – Walter

risposta

2

penso che questo funzionerà per voi. Per prima cosa definiamo una funzione membro virtuale pura call_bar in modo che possiamo chiamarla sulla classe base A. Per l'implementazione abbiamo bisogno del tipo giusto ma con CRTP la classe utente può specificarlo per noi.

class A 
{ 
public: 
    virtual void call_bar() = 0; 
    virtual ~A() {} 
}; 

template <typename T> 
class B : public A 
{ 
public: 
    virtual void call_bar() override 
    { 
    bar(static_cast<T*>(this)); 
    } 
}; 

class C: public B<C> 
{ 
}; 
+0

Questa tecnica sembra che funzionerà sicuramente per me. L'unico problema che sto avendo è che quando provo a creare un vettore di oggetti 'A', il mio compilatore mi dice che devo specificare i parametri del template. L'errore completo è: 'argomento template 1 non valido 'sulla riga che ho:' typedef std :: vector a_list; '. Questa tecnica funzionerà ancora se dico 'std :: vector > a_list;'? – AFlyingCar

+0

'A' non dovrebbe essere un modello hai saltato' B'? Ecco un esempio di utilizzo in [Ideone] (https://ideone.com/x58ml0) @AFlyingCar – Drek

+0

Le mie scuse. Avevo modificato leggermente la soluzione per adattarla meglio al mio progetto, ma quando lo facevo l'ho accidentalmente applicato a 'B' anziché a' A' (senza accorgersene). L'ho risolto ora però. Grazie ancora per il tuo aiuto. – AFlyingCar

4

Questo è ciò che i metodi virtuali sono per.

Dichiara il metodo virtuale foo() in A e non devi preoccuparti di fare brutti cast.

In alternativa, se A ha almeno una funzione virtuale - e un distruttore virtuale lo farà - puoi tentare la fortuna con un dynamic_cast<>.

3

In C++, (dinamico) polymorphism è lo strumento per questo tipo di cose.

struct A { 
    virtual void foo() = 0; 
    virtual~A() {} 
}; 

struct B : A { 
    void foo() override; 
}; 

std::vector<std::unique_ptr<A>> vA; 
vA.emplace_back(new B); 
vA[0]->foo(); 

v'è alcuna necessità di trovare il tipo effettivo dell'oggetto puntato da vA[0], il meccanismo di richiamo virtual funzione troverà la funzione corretta (in questo caso B::foo()). Notare il distruttore virtual di class A. Garantisce che gli oggetti derivati ​​(di class B) vengono correttamente eliminati da un puntatore a A (come mantenuto da unique_ptr<A>).

Naturalmente, c'è anche la dynamic_cast<>, che permette di testare se l'oggetto è in realtà di un certo tipo:

auto ptr = dynamic_cast<B*>(vA[0].get()); 
if(ptr) // object is a B 
    std::cout<<" have B"; 
+1

Penso che l'uso di "unique_ptr" in questa altrimenti ottima risposta sia fuori luogo. Non è quello che insegnerei a un principiante a causa dei suoi sorprendenti effetti collaterali. Se devi usare un puntatore intelligente, usa invece un 'shared_ptr' perché si comporta in modo simile a come qualcuno si aspetterebbe che un puntatore si comporti. No +1 da me. – Fozi

+1

Non sono d'accordo con il commento sopra. 'unique_ptr' è quello che si dovrebbe usare di default per contenere oggetti allocati dinamicamente. Possedere i puntatori non elaborati è fuori questione e 'shared_ptr's è eccessivo fino a quando non si ha effettivamente bisogno della loro complessità aggiunta. – Quentin

+0

@Fozi Il mio modo di pensare era più o meno sulla falsariga del commento di Quentin. Non sono sicuro di quali "effetti collaterali sorprendenti" ti riferisci. IMHO, 'shared_ptr' è ragionevole solo se la proprietà deve essere condivisa (e più di un tale puntatore è potenzialmente creato per un singolo oggetto). Questo non è il caso qui. – Walter

0

La soluzione che ho pensato che potrebbe essere necessario è quello di determinare in qualche modo il tipo che ogni oggetto è in fase di runtime, in modo che io possa lanciarlo prima di passarlo alla barra.

dynamic_cast è quello che state cercando:

class A 
{ 
public: 
    virtual ~A() {} 
}; 

class B: public A 
{ 
public: 
    void foo() { 
    printf("B::foo()\n"); 
    } 
}; 

... 

std::vector<A*> v; 
v.push_back(new B); 

... 

A *a = v.get(0); 
B *b = dynamic_cast<B*>(a); 
if (b) 
    bar(b);