2010-10-08 13 views
16

C'è un motivo per cui è specificato che std::type_info sia polimorfico? Il distruttore è specificato per essere virtuale (e c'è un commento all'effetto di "in modo che sia polimorfico" in The Design and Evolution of C++). Non riesco davvero a vedere un motivo convincente per cui. Non ho alcun caso d'uso specifico, mi stavo chiedendo se c'è mai stato un fondamento logico o una storia dietro di esso.Perché è std :: type_info polimorfico?


Ecco alcune idee che mi è venuta in mente e respinto:

  1. E 'un punto di estensibilità - implementazioni potrebbero definire sottoclassi, e programmi potrebbe quindi provare a dynamic_cast un std::type_info ad un altro, specifica di esecuzione definito tipo derivato. Questa è probabilmente la ragione, ma sembra che sia altrettanto facile per le implementazioni aggiungere un membro definito dall'implementazione, che potrebbe essere virtuale. I programmi che desiderano testare queste estensioni sarebbero necessariamente non portatili.
  2. È per garantire che i tipi derivati ​​siano distrutti correttamente quando delete un puntatore di base. Ma non ci sono tipi derivati ​​standard, gli utenti non possono definire tipi derivati ​​utili, perché type_info non ha costruttori pubblici standard e quindi delete un puntatore type_info non è mai sia legale che portatile. E i tipi derivati ​​non sono utili perché non possono essere costruiti - l'unico uso che conosco per tali tipi derivati ​​non costruibili è l'implementazione di cose come il tratto di tipo is_polymorphic.
  3. Lascia aperta la possibilità di metaclassi con tipi personalizzati: ogni reale polimorfo class A otterrebbe un "metaclasse" derivato A__type_info, che deriva da type_info. Forse tali classi derivate potrebbero esporre membri che chiamano new A con vari argomenti del costruttore in un modo sicuro dal tipo e cose del genere. Ma rendere lo stesso polimorfico type_info rende praticamente impossibile l'implementazione di tale idea, perché dovresti avere metaclassi per i tuoi metaclassi, ad infinitum, il che è un problema se tutti gli oggetti type_info hanno una durata di archiviazione statica. Forse escludendo questa è la ragione per averlo reso polimorfico.
  4. C'è qualche utilità per l'applicazione di caratteristiche RTTI (diversi dynamic_cast) per std::type_info stesso, o qualcuno ha pensato che fosse carino, o imbarazzante se type_info non era polimorfica. Ma dato che non esiste un tipo derivato standard e nessun'altra classe nella gerarchia standard a cui si potrebbe ragionevolmente provare a eseguire il cross-cast, la domanda è: che cosa? Esiste un uso per espressioni come typeid(std::type_info) == typeid(typeid(A))?
  5. È perché gli implementatori creeranno il proprio tipo derivato privato (come credo che GCC lo faccia). Ma perché preoccuparsi di specificarlo? Anche se il distruttore non è stato specificato come virtuale e un implementatore ha deciso che dovrebbe essere, sicuramente quell'implementazione potrebbe dichiararlo virtuale, perché non modifica l'insieme delle operazioni consentite su type_info, quindi un programma portatile non sarebbe in grado per dire la differenza
  6. È qualcosa a che fare con compilatori con ABI parzialmente compatibili coesistenti, probabilmente come risultato del collegamento dinamico. Forse gli implementatori potrebbero riconoscere la propria sottoclasse type_info (a differenza di quella proveniente da un altro fornitore) in modo portatile se fosse garantito che lo type_info fosse virtuale.

L'ultimo è il più plausibile per me al momento, ma è piuttosto debole.

risposta

9

Penso che sia lì per la comodità di esecutori. Consente loro di definire le classi estese type_info ed eliminarle tramite puntatori su type_info all'uscita del programma, senza dover creare speciali magie del compilatore per chiamare il distruttore corretto, o altrimenti saltare attraverso i cerchi.

sicuramente che l'attuazione potrebbe dichiarano che virtuale, in quanto non modificare il set di operazioni consentite su type_info, quindi un programma portatile non sarebbe in grado di capire la differenza .

Non penso sia vero. Si consideri il seguente:

#include <typeinfo> 

struct A { 
    int x; 
}; 

struct B { 
    int x; 
}; 

int main() { 
    const A *a1 = dynamic_cast<const A*>(&typeid(int)); 
    B b; 
    const A *a2 = dynamic_cast<const A*>(&b); 
} 

Che si tratti di ragionevole o no, è consentito il primo cast dinamica (e restituisce un puntatore nullo), mentre il secondo cast dinamica non è permesso. Quindi, se nello standard è stato definito type_info per avere il distruttore non virtuale predefinito, ma un'implementazione ha aggiunto un distruttore virtuale, un programma portatile potrebbe rilevare la differenza [*].

Sembra più semplice per me di mettere il distruttore virtuale nella norma, oltre a uno:

a) mettere una nota nella norma che, anche se la definizione di classe implica che type_info non ci sono funzioni virtuali, è consentito avere un distruttore virtuale.

b) determinare l'insieme di programmi che possono distinguere se type_info è polimorfico o meno, e vietarli tutti. Potrebbero non essere programmi molto utili o produttivi, non lo so, ma per metterli al bando devi trovare un linguaggio standard che descriva l'eccezione specifica che stai facendo alle normali regole.

Quindi penso che lo standard debba o mandare il distruttore virtuale, o vietarlo. Il fatto di renderlo facoltativo è troppo complesso (o forse dovrei dire, penso che sarebbe giudicato inutilmente complesso. La complessità non ha mai fermato il comitato degli standard in aree in cui è stato considerato utile ...)

Se è stato vietato, però, quindi un'implementazione potrebbe:

  • aggiungere un distruttore virtuale a qualche classe derivata di type_info
  • derivare tutti i suoi oggetti typeinfo da che classe
  • Usalo internamente come classe di base polimorfa per tutto

che risolverebbe la situazione nella parte superiore del post, ma il tipo statico di un'espressione typeid sarebbe ancora const std::type_info, quindi sarebbe difficile per le implementazioni definire estensioni dove i programmi possono dynamic_cast per vari obiettivi per vedere che tipo di oggetto type_info hanno in un caso particolare. Forse lo standard spera di consentire che, sebbene un'implementazione potrebbe offrire sempre una variante di typeid con un tipo statico differente, o garantire che un static_cast ad una certa classe di estensione funzionerà, e poi lasciare che il programma dynamic_cast da lì.

In sintesi, per quanto ne so il distruttore virtuale è potenzialmente utile per gli esecutori, e la rimozione non guadagna niente a nessuno diverso da quello che noi non avremmo trascorso tempo chiedendo perché è lì ;-)

[*] In realtà, non ho dimostrato. Ho dimostrato che un programma illegale sarebbe, a parità di condizioni, compilato. Ma un'implementazione potrebbe forse risolvere che garantendo che non tutto è uguale, e che non compilazione. Boost is_polymorphic non è portatile, così, mentre è possibile che un programma per testare che una classe è polimorfico, che dovrebbe essere, ci può essere alcun modo per un programma conforme a verificare che una classe non è polimorfica, che non dovrebbe essere Credo però che, anche se è impossibile, dimostrando che, al fine di rimuovere una linea dallo standard, è un bel po 'di fatica.

9

Lo standard C++ dice che typeid restituisce un oggetto di tipo tipo_info, O UNA sottoclasse IMPLEMENTATION-DEFINED di esso. Quindi ... Immagino che questa sia praticamente la risposta.Quindi non vedo il motivo per cui si rifiuta i punti 1 e 2.

Paragrafo 5.2.8 Clausola 1 della corrente C++ standard di legge:

Il risultato di un'espressione typeid è un lvalue di statica tipo const std :: type_info (18.5.1) e dinamico tipo const std :: type_info o const nome dove nome è una classe di implementazione definita derivato da std :: type_info che conserva il problema descritto in 18.5. 1.61) La vita utile dell'oggetto dal lvalue si estende fino alla fine di il programma. Indipendentemente dal fatto che il distruttore venga chiamato per l'oggetto type_info alla fine del programma è non specificato.

Che a sua volta significa che si potrebbe scrivere il seguente codice è legale e fine: const type_info& x = typeid(expr); che può richiedere che type_info essere polimorfico

+1

Sì, ho capito, ma non riesco a vedere nulla di portatile che si possa fare con le informazioni che si tratta di una sottoclasse o meno. Sembra che tutti i programmi validi e portatili ora sarebbero ancora validi se non fossero polimorfi. Fare in modo che non sia possibile rilevare sottoclassi (rendendo type_info non polimorfo) non dovrebbe cambiare nulla per i programmi portatili, per quanto posso vedere, e non dovrebbe rendere più difficile creare estensioni non portabili. Riesci a pensare ad un contro-esempio, a qualcosa reso possibile da questa specifica? – Doug

+0

@Doug: guarda la mia modifica –

+1

Grazie - ma continuo a non capire perché quest'ultima espressione richiede che 'type_info' sia polimorfico. Tutte le funzioni utili su 'type_info', come' operator == ',' before', 'name', ecc. Non sono specificate come virtuali. Quindi, se un'implementazione richiede una distribuzione virtuale su tali funzioni, suppongo che possa dichiararle virtuali o delegare a funzioni di implementazione virtuale e, in caso contrario, non è necessario renderle virtuali. C'è qualcosa che mi manca? – Doug

2

L'ID "globale" più semplice che è possibile avere in C++ è un nome di classe e typeinfo fornisce un modo per confrontare tali ID per l'uguaglianza. Ma il disegno è così imbarazzante e limitata che è quindi necessario per avvolgere typeinfo in qualche classe wrapper, per esempio essere in grado di inserire istanze nelle raccolte. Andrei Alexandrescu fatto che nel suo "moderno C++ Design" e penso che che typeinfo wrapper è parte della libreria di Loki; ce n'è probabilmente anche uno in Boost; ed è piuttosto facile eseguire il rollover, ad es. vedi my own wrapper.

Ma anche per tale wrapper non c'è in generale alcuna necessità di un distruttore virtuale in typeinfo.

La domanda quindi non è tanto "eh, perché c'è un distruttore virtuale", ma piuttosto, come la vedo io, "eh, perché il design è così arretrato, imbarazzante e non direttamente utilizzabile"? E lo metterei nel processo di standardizzazione. Ad esempio, iostream non sono esattamente esempi di design superbo, neanche; non qualcosa da emulare.

+1

Ho una risposta "eh, perché il design è così arretrato, scomodo e non direttamente utilizzabile?". È fondamentalmente perché Bjarne Stroustrup (a) voleva scoraggiare le persone dal basarsi troppo su RTTI, e (b) ottenere molte richieste per tutti i tipi di funzionalità relative a RTTI, alcune delle quali violavano la sicurezza del tipo di C++. Pensava che sarebbe stato un grosso problema soddisfare le richieste di tutti, oltre ad essere potenzialmente di alto livello e tecnicamente discutibili, per cui era più facile fornire semplicemente una caratteristica essenziale che le persone avrebbero potuto sviluppare secondo le loro esigenze. – Doug

2

3/lascia aperta la possibilità di metaclassi con tipi personalizzati - ogni reale polimorfica class A otterrebbe un derivato "metaclasse" A__type_info, che deriva da type_info. Forse tali classi derivate potrebbero esporre membri che chiamano new A con vari argomenti del costruttore in un modo sicuro dal tipo e cose del genere.Ma rendere lo stesso polimorfico type_info rende praticamente impossibile l'implementazione di tale idea, perché dovresti avere metaclassi per i tuoi metaclassi, ad infinitum, il che è un problema se tutti gli oggetti type_info hanno una durata di archiviazione statica. Forse escludendo questa è la ragione per averlo reso polimorfico.

Clever ...

In ogni caso, sono d'accordo con questo ragionamento: tale realizzazione potrebbe facilmente escludere-out classi meta per i tipi di derivati ​​da type_info, tra cui type_info stesso.

Problemi correlati