2009-07-10 5 views
7

Abbiamo un sottoprogetto "commonUtils" che ha molti snippet di codice generici utilizzati nel progetto principale. Una di queste cose interessanti che ho visto era: -Verificare se una classe è polimorfica

/********************************************************************* 
If T is polymorphic, the compiler is required to evaluate the typeid 
stuff at runtime, and answer will be true. If T is non-polymorphic, 
the compiler is required to evaluate the typeid stuff at compile time, 
whence answer will remain false 
*********************************************************************/ 
template <class T> 
bool isPolymorphic() { 
    bool answer=false; 
    typeid(answer=true,T()); 
    return answer; 
} 

ho creduto il commento e ho pensato che è piuttosto un modello interessante anche se non è utilizzato in tutto il progetto. Ho provato ad usarlo in questo modo solo per curiosità ...

class PolyBase { 
public: 
    virtual ~PBase(){} 
}; 

class NPloyBase { 
public: 
    ~NBase(){} 
}; 


if (isPolymorphic<PolyBase>()) 
    std::cout<<"PBase = Polymorphic\n"; 
if (isPolymorphic<NPolyBase>()) 
    std::cout<<"NBase = Also Polymorphic\n"; 

Ma nessuno di quelli restituisce mai vero. MSVC 2005 non dà avvisi ma Comeau avverte che l'espressione di tipo non ha alcun effetto. La sezione 5.2.8 nello standard C++ non dice nulla di simile a ciò che il commento dice che il typeid è valutato al momento della compilazione per i tipi non polimorfici e al runtime per i tipi polimorfici.

1) Quindi immagino che il commento sia fuorviante/errato o che l'autore di questo codice sia un programmatore C++ piuttosto anziano, mi manca qualcosa?

2) OTOH, mi chiedo se possiamo verificare se una classe è polimorfica (ha almeno una funzione virtuale) utilizzando una tecnica?

3) Quando si vorrebbe sapere se una classe è polimorfica? Ipotesi selvaggia; per ottenere l'indirizzo iniziale di una classe utilizzando dynamic_cast<void*>(T) (come dynamic_cast funziona solo su classi polimorfiche).

In attesa di pareri.

Grazie in anticipo,

+0

E, se l'autore è un programmatore C++ senior, perché non lo controlli prima con lui? ... Spesso imparerai molto dai ragazzi esperti. – stefanB

+9

Beh, se potessi non lo avrei chiesto su StackOverflow :-) – Abhay

risposta

8

Non riesco a immaginare alcun modo possibile come questo typeid potrebbe essere utilizzato per controllare che tipo è polimorfico. Non può nemmeno essere usato per affermare che lo è, dal momento che typeid funzionerà su qualsiasi tipo. Boost ha un'implementazione here. Per quanto riguarda il motivo per cui potrebbe essere necessario - un caso che conosco è la libreria Boost.Serialization. Se stai salvando il tipo non polimorfico, puoi semplicemente salvarlo. Se si sta salvando uno polimorfo, si deve ottenere il suo tipo dinamico usando typeid, e quindi invocare il metodo di serializzazione per quel tipo (cercandolo in una tabella).

Aggiornamento: sembra che io sia effettivamente sbagliato. Considerate questa variante:

template <class T> 
bool isPolymorphic() { 
    bool answer=false; 
    T *t = new T(); 
    typeid(answer=true,*t); 
    delete t; 
    return answer; 
} 

Questo in realtà funziona come suggerisce il nome, esattamente per commentare in snippet di codice originale. L'espressione all'interno di typeid non viene valutata se "non designa un lvalue di tipo di classe polimorfico" (std 3.2/2). Quindi, nel caso precedente, se T non è polimorfico, l'espressione di tipo non viene valutata. Se T è polimorfico, allora * t è effettivamente lvalue di tipo polimorfico, quindi deve essere valutata l'intera espressione.

Ora, il tuo esempio originale è ancora sbagliato :-). Ha utilizzato T(), non *t. E T() crea rvalore (std 3.10/6). Quindi, produce ancora un'espressione che non è "lvalue della classe polimorfica".

Questo è un trucco abbastanza interessante. D'altra parte, il suo valore pratico è alquanto limitato - perché mentre boost :: is_polymorphic fornisce una costante in fase di compilazione, questo ti dà un valore di run-time, quindi non puoi istanziare un codice diverso per i tipi polimorfici e non polimorfici .

+0

Sì, conosco l'implementazione boost che utilizza approssimativamente la tecnica sizeof(). Grazie per l'assaggio della serializzazione. Mi interessava sapere se il modello commonUtils è corretto in primo luogo e in secondo luogo vale la pena preservarlo nel progetto. – Abhay

+2

Ah ah, un trucco davvero interessante, ma la tua citazione del 3.10/6 è stata illuminante, grazie. Si può scegliere la versione templatizzata quando le dimensioni di una dimensione binaria o se l'utente non deve essere considerato attendibile fornendo un virtual dtor in una classe polimorfica. Ahimè non posso andare su 2 volte! – Abhay

3


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

class NPolyBase { 
public: 
    ~NPolyBase(){} 
}; 

template<class T> 
struct IsPolymorphic 
{ 
    struct Derived : T { 
     virtual ~Derived(); 
    }; 
    enum { value = sizeof(Derived)==sizeof(T) }; 
}; 


void ff() 
{ 
    std::cout << IsPolymorphic<PolyBase >::value << std::endl; 
    std::cout << IsPolymorphic<NPolyBase>::value << std::endl; 
} 

+1

Non sono sicuro se questo è completamente infallibile. Un compilatore può aggiungere padding tra sotto oggetti, nel qual caso il trucco sizeof() non funzionerebbe. – Abhay

+1

Questo si interromperebbe quando, ad esempio, il derivato definisce le proprie variabili membro, rendendolo poco pratico. – Indy9000

+0

@Indeera: non si aggiungerebbero variabili membro in quanto la struct Derivato deriva intenzionalmente dalla classe specificata dall'utente. L'unica avvertenza è se la classe specificata non ha un virtual dtor ma alcune funzioni virtuali (nel qual caso la classe specificata è ancora polimorfa) a parte il problema del padding. Boost presuppone che una classe polimorfica definisca un virtual dtor e gestisca il padding utilizzando alcune specifiche del compilatore, suppongo. – Abhay

-1

Sono un po 'confuso qui e spero di ottenere alcuni commenti su questa risposta che spiegano cosa mi manca.

Sicuramente se si desidera scoprire se una classe è polimorfica, tutto ciò che si deve fare è chiedere se supporta dynamic_cast, non è vero?

template<class T, class> struct is_polymorphic_impl : false_type {}; 
template<class T> struct is_polymorphic_impl 
    <T, decltype(dynamic_cast<void*>(declval<T*>()))> : true_type {}; 

template<class T> struct is_polymorphic : 
    is_polymorphic_impl<remove_cv_t<T>, void*> {}; 

Qualcuno può segnalare un difetto in questa implementazione? Immagino che ci debba essere uno, o deve essere stato uno ad un certo punto nel passato, perché the Boost documentation continua a sostenere che is_polymorphic "non può essere implementato in modo passivo nel linguaggio C++".

Ma "portabilmente" è una specie di parola di donnola, giusto? Forse stanno solo alludendo a come MSVC non supporta l'espressione SFINAE, o alcuni dialetti come Embedded C++ non supportano dynamic_cast. Forse quando dicono "linguaggio C++" significano "un sottoinsieme di minimo comune denominatore del linguaggio C++". Ma ho il fastidioso sospetto che forse intendano quello che dicono, e Sono quello che manca qualcosa.

L'approccio typeid nel PO (come modificato da una risposta in seguito di utilizzare un non lvalue un rvalue) sembra anche bene, ma naturalmente non è constexpr e richiede in realtà la costruzione di un T, che potrebbe essere super costoso. Quindi questo approccio dynamic_cast sembra migliore ... a meno che non funzioni per qualche motivo. Pensieri?