Dal punto di vista del design, perché in C ++ non esiste una classe base madre di tutti, cosa succede di solito object
negli altri linguaggi?
typedef
. Con una gerarchia di classi più generica, potresti semplicemente usare object
o iterator
.
Dal punto di vista del design, perché in C ++ non esiste una classe base madre di tutti, cosa succede di solito object
negli altri linguaggi?
typedef
. Con una gerarchia di classi più generica, potresti semplicemente usare object
o iterator
.
Risposte:
La sentenza definitiva si trova nelle FAQ di Stroustrup . In breve, non trasmette alcun significato semantico. Avrà un costo. I modelli sono più utili per i contenitori.
Perché C ++ non ha un oggetto di classe universale?
Non ne abbiamo bisogno: la programmazione generica fornisce alternative staticamente sicure nella maggior parte dei casi. Altri casi vengono gestiti utilizzando l'ereditarietà multipla.
Non esiste una classe universale utile: un vero universale non ha una semantica propria.
Una classe "universale" incoraggia a pensare in modo sciatto a tipi e interfacce e porta a un controllo del tempo di esecuzione eccessivo.
L'uso di una classe base universale implica un costo: gli oggetti devono essere allocati nell'heap per essere polimorfici; ciò implica memoria e costi di accesso. Gli oggetti heap non supportano naturalmente la semantica della copia. Gli oggetti heap non supportano un comportamento con ambito semplice (che complica la gestione delle risorse). Una classe base universale incoraggia l'uso di dynamic_cast e altri controlli in fase di esecuzione.
Objects must be heap-allocated to be polymorphic
- Non credo che questa affermazione sia generalmente corretta. Puoi sicuramente creare un'istanza di una classe polimorfica sullo stack e passarla come puntatore a una delle sue classi base, ottenendo un comportamento polimorfico.
Foo
in un riferimento a Bar
quando Bar
non eredita Foo
, genera in modo deterministico un'eccezione. In C ++, il tentativo di lanciare un pointer-to-Foo in un pointer-to-Bar potrebbe mandare un robot indietro nel tempo per uccidere Sarah Connor.
Pensiamo prima al motivo per cui vorresti avere una classe base in primo luogo. Posso pensare a diversi motivi:
Questi sono i due buoni motivi per cui i linguaggi del marchio Smalltalk, Ruby e Objective-C hanno classi base (tecnicamente, Objective-C non ha realmente una classe base, ma a tutti gli effetti, lo fa).
Per # 1, la necessità di una classe base che unifica tutti gli oggetti in un'unica interfaccia è ovviata dall'inclusione di modelli in C ++. Per esempio:
void somethingGeneric(Base);
Derived object;
somethingGeneric(object);
non è necessario, quando è possibile mantenere l'integrità del tipo fino in fondo per mezzo del polimorfismo parametrico!
template <class T>
void somethingGeneric(T);
Derived object;
somethingGeneric(object);
Per # 2, mentre in Objective-C le procedure di gestione della memoria fanno parte dell'implementazione di una classe e sono ereditate dalla classe base, la gestione della memoria in C ++ viene eseguita utilizzando la composizione anziché l'ereditarietà. Ad esempio, puoi definire un wrapper puntatore intelligente che eseguirà il conteggio dei riferimenti su oggetti di qualsiasi tipo:
template <class T>
struct refcounted
{
refcounted(T* object) : _object(object), _count(0) {}
T* operator->() { return _object; }
operator T*() { return _object; }
void retain() { ++_count; }
void release()
{
if (--_count == 0) { delete _object; }
}
private:
T* _object;
int _count;
};
Quindi, invece di chiamare metodi sull'oggetto stesso, dovresti chiamare metodi nel suo wrapper. Questo non solo consente una programmazione più generica: consente anche di separare le preoccupazioni (poiché idealmente, il tuo oggetto dovrebbe essere più preoccupato di ciò che dovrebbe fare, piuttosto che di come la sua memoria dovrebbe essere gestita in diverse situazioni).
Infine, in un linguaggio che ha sia primitive che oggetti reali come C ++, i vantaggi di avere una classe base (un'interfaccia coerente per ogni valore) vengono persi, poiché allora si hanno determinati valori che non possono essere conformi a quell'interfaccia. Per poter usare le primitive in quel tipo di situazione, devi sollevarle in oggetti (se il tuo compilatore non lo farà automaticamente). Questo crea molte complicazioni.
Quindi, la risposta breve alla tua domanda: C ++ non ha una classe base perché, avendo il polimorfismo parametrico attraverso i modelli, non ne ha bisogno.
object
( System.Object
), ma non è necessario che lo siano. Per il compilatore, int
e System.Int32
sono alias e possono essere usati in modo intercambiabile; il runtime gestisce la boxe quando necessario.
std::shared_ptr
quale dovrebbe essere usato invece.
Il paradigma dominante per le variabili C ++ è il valore di passaggio, non il riferimento di passaggio. Forzare tutto a derivare da una radice Object
renderebbe il passaggio per valore un errore ipse facto.
(Perché accettare un oggetto per valore come parametro, per definizione lo taglierebbe e rimuoverebbe la sua anima).
Questo non è gradito. C ++ ti fa pensare se volevi valore o semantica di riferimento, dandoti la scelta. Questa è una cosa importante nel performance computing.
Object
sarebbe relativamente basso .
Il problema è che esiste un tale tipo in C ++! Lo è void
. :-) È possibile eseguire il cast implicitamente di qualsiasi puntatore void *
, inclusi i puntatori a tipi di base, classi senza tabella virtuale e classi con tabella virtuale.
Poiché dovrebbe essere compatibile con tutte quelle categorie di oggetti, esso void
stesso non può contenere metodi virtuali. Senza funzioni virtuali e RTTI, non è possibile ottenere informazioni utili sul tipo void
(corrisponde a OGNI tipo, quindi può dire solo cose che sono vere per OGNI tipo), ma le funzioni virtuali e RTTI renderebbero i tipi semplici molto inefficaci e impedirebbero a C ++ di essere un linguaggio adatto per la programmazione di basso livello con accesso diretto alla memoria ecc.
Quindi, c'è questo tipo. Fornisce solo un'interfaccia molto minimalista (in effetti vuota) a causa della natura di basso livello del linguaggio. :-)
void
.
void
non verrà compilata e otterrai questo errore . In Java potresti avere una variabile di tipo Object
e funzionerebbe. Questa è la differenza tra void
e un tipo "reale". Forse è vero che void
è il tipo base per tutto, ma poiché non fornisce alcun costruttore, metodo o campo, non c'è modo di dire che sia presente o meno. Questa affermazione non può essere provata o confutata.
void
è un tipo (e scoperto un uso in? :
qualche modo intelligente ), ma è ancora molto lontano dall'essere una classe base universale. Per prima cosa, è "un tipo incompleto che non può essere completato" e per un altro, non esiste alcun void&
tipo.
Il C ++ è un linguaggio fortemente tipizzato. Eppure è sconcertante che non abbia un tipo di oggetto universale nel contesto della specializzazione dei modelli.
Prendi, ad esempio, il modello
template <class T> class Hook;
template <class ReturnType, class ... ArgTypes>
class Hook<ReturnType (ArgTypes...)>
{
...
ReturnType operator () (ArgTypes... args) { ... }
};
che può essere istanziato come
Hook<decltype(some_function)> ...;
Supponiamo ora di volere lo stesso per una particolare funzione. Piace
template <auto fallback> class Hook;
template <auto fallback, class ReturnType, class ... ArgTypes>
class Hook<ReturnType fallback(ArgTypes...)>
{
...
ReturnType operator () (ArgTypes... args) { ... }
};
con l'istanza specializzata
Hook<some_function> ...
Ma ahimè, anche se la classe T può sostituire qualsiasi tipo (classe o meno) prima della specializzazione, non c'è equivalente auto fallback
(sto usando quella sintassi come la più ovvia generica non di tipo in questo contesto) che potrebbe sostituire qualsiasi argomento del modello non di tipo prima della specializzazione.
Quindi, in generale, questo modello non viene trasferito da argomenti di modello di tipo a argomenti di modello non di tipo.
Come con molti angoli del linguaggio C ++, la risposta è probabilmente "nessun membro del comitato ci ha pensato".
ReturnType fallback(ArgTypes...)
funzionasse, sarebbe un cattivo design. template <class T, auto fallback> class Hook; template <class ReturnType, class ... ArgTypes> class Hook<ReturnType (ArgTypes...), ReturnType(*fallback)(ArgTypes...)> ...
fa quello che vuoi e significa che i parametri del modello Hook
sono di tipo
Il C ++ era inizialmente chiamato "C con classi". È una progressione del linguaggio C, a differenza di altre cose più moderne come C #. E non puoi vedere il C ++ come un linguaggio, ma come un fondamento dei linguaggi (Sì, sto ricordando il libro di Scott Meyers Effective C ++).
Il C stesso è un mix di linguaggi, il linguaggio di programmazione C e il suo preprocessore.
C ++ aggiunge un altro mix:
l'approccio classe / oggetti
modelli
il STL
Personalmente non mi piacciono alcune cose che provengono direttamente da C a C ++. Un esempio è la funzione enum. Il modo in cui C # consente allo sviluppatore di usarlo è decisamente migliore: limita l'enumerazione nel proprio ambito, ha una proprietà Count ed è facilmente iterabile.
Poiché il C ++ voleva essere retrocompatibile con C, il designer è stato molto permissivo nel consentire al linguaggio C di entrare nella sua interezza in C ++ (ci sono alcune sottili differenze, ma non ricordo nulla che potresti fare usando un compilatore C che hai non poteva fare usando un compilatore C ++).