Typedef interni in C ++: stile buono o stile cattivo?


179

Qualcosa che mi sono ritrovato a fare spesso ultimamente è dichiarare dattiloscritti rilevanti per una particolare classe all'interno di quella classe, cioè

class Lorem
{
    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
};

Questi tipi vengono quindi utilizzati altrove nel codice:

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

Ragioni che mi piacciono:

  • Riduce il rumore introdotto dai modelli di classe, std::vector<Lorem>diventa Lorem::vector, ecc.
  • Serve come una dichiarazione di intenti: nell'esempio sopra, la classe Lorem deve essere contata come riferimento boost::shared_ptre memorizzata in un vettore.
  • Permette di cambiare l'implementazione, ovvero se Lorem dovesse essere modificato per essere contato in modo intrusivo boost::intrusive_ptrin un secondo momento (via ), ciò avrebbe un impatto minimo sul codice.
  • Penso che appaia "più bello" ed è probabilmente più facile da leggere.

Ragioni che non mi piacciono:

  • A volte ci sono problemi con le dipendenze - se vuoi incorporare, diciamo, Lorem::vectorall'interno di un'altra classe ma devi solo (o vuoi) inoltrare dichiarare Lorem (invece di introdurre una dipendenza sul suo file di intestazione), allora devi finire per usare tipi espliciti (ad es. boost::shared_ptr<Lorem>anziché Lorem::ptr), che è un po 'incoerente.
  • Potrebbe non essere molto comune, e quindi più difficile da capire?

Cerco di essere obiettivo con il mio stile di programmazione, quindi sarebbe bene avere qualche altra opinione su di esso in modo da poter analizzare un po 'il mio pensiero.

Risposte:


153

Penso che sia uno stile eccellente e lo uso da solo. È sempre meglio limitare l'ambito dei nomi il più possibile e l'uso delle classi è il modo migliore per farlo in C ++. Ad esempio, la libreria C ++ Standard fa un uso intenso dei typedef all'interno delle classi.


Questo è un buon punto, mi chiedo che il mio subconscio sembrasse "più carino" sottolineando delicatamente che la portata limitata è una buona cosa. Mi chiedo però, il fatto che lo STL lo usi prevalentemente nei modelli di classe lo rende un utilizzo leggermente diverso? È più difficile giustificare in una classe "concreta"?
Will Baker,

1
Bene, la libreria standard è composta da modelli piuttosto che da classi, ma penso che la giustificazione sia la stessa per entrambi.

14

Serve come una dichiarazione di intenti: nell'esempio sopra, la classe Lorem deve essere contata come riferimento tramite boost :: shared_ptr e memorizzata in un vettore.

Questo è esattamente ciò che fa non fa.

Se vedo 'Foo :: Ptr' nel codice, non ho assolutamente idea se si tratta di un shared_ptr o di un Foo * (STL ha :: typedef puntatore che sono T *, ricordi) o altro. Esp. se si tratta di un puntatore condiviso, non fornisco affatto un typedef, ma mantengo esplicitamente l'uso di shared_ptr nel codice.

In realtà, non uso quasi mai i typedef al di fuori della metaprogrammazione dei modelli.

L'STL fa sempre questo tipo di cose

Il design STL con concetti definiti in termini di funzioni membro e typedef annidati è un vicolo cieco storico, le moderne librerie di template usano funzioni gratuite e classi di tratti (cfr. Boost.Graph), perché questi non escludono i tipi incorporati da modellando il concetto e perché rende più semplice l'adattamento di tipi che non sono stati progettati tenendo conto dei concetti delle librerie di modelli.

Non utilizzare l'STL come motivo per fare gli stessi errori.


Sono d'accordo con la tua prima parte, ma la tua modifica recente è un po 'miope. Tali tipi nidificati semplificano la definizione di classi di tratti, in quanto forniscono un valore predefinito ragionevole. Considera la nuova std::allocator_traits<Alloc>classe ... non devi specializzarla per ogni singolo allocatore che scrivi, perché prende semplicemente in prestito i tipi direttamente da Alloc.
Dennis Zickefoose,

@Dennis: in C ++, la convenienza dovrebbe essere dalla parte di / user / di una libreria, non dalla parte di / author /: l'utente desidera un'interfaccia uniforme per un tratto, e solo una classe di tratti può darlo, per i motivi sopra indicati). Ma anche come Allocautore, non è esattamente più difficile specializzarsi std::allocator_traits<>per il suo nuovo tipo di quanto non sia aggiungere i typedef necessari. Ho anche modificato la risposta, perché la mia risposta completa non rientrava in un commento.
Marc Mutz - mmutz,

Ma è dalla parte dell'utente. In qualità di utente del allocator_traitstentativo di creare un allocatore personalizzato, io non devo perdere tempo con i quindici membri della classe tratti ... tutto quello che devo fare è dire typedef Blah value_type;e fornire le funzioni membro appropriate, e il default allocator_traitssarà capire il riposo. Inoltre, guarda il tuo esempio di Boost.Graph. Sì, fa un uso pesante della classe tratti ... ma l'implementazione predefinita di graph_traits<G>semplici query Gper i propri typedef interni.
Dennis Zickefoose,

1
E anche la libreria standard 03 fa uso di classi di tratti laddove appropriato ... la filosofia della biblioteca non è quella di operare su contenitori genericamente, ma di operare su iteratori. Quindi fornisce una iterator_traitsclasse in modo che i tuoi algoritmi generici possano facilmente interrogare per le informazioni appropriate. Il che, ancora una volta, viene automaticamente impostato come query sull'iteratore per le proprie informazioni. Il lungo e breve è che i tratti e i typedef interni non si escludono a vicenda ... si sostengono a vicenda.
Dennis Zickefoose,

1
@Dennis: è iterator_traitsdiventato necessario perché T*dovrebbe essere un modello di RandomAccessIterator, ma non puoi inserire i typedef richiesti T*. Una volta che abbiamo avuto iterator_traits, i typedef nidificati sono diventati ridondanti e vorrei che fossero stati rimossi lì e poi. Per lo stesso motivo (impossibilità di aggiungere typedef interni), T[N]non modella il Sequenceconcetto STL e hai bisogno di kludges come std::array<T,N>. Boost.Range mostra come definire un moderno concetto di sequenza in T[N]grado di modellare, poiché non richiede typedef nidificati né funzioni membro.
Marc Mutz - mmutz,


8

I typdef sono sicuramente di buon stile. E tutte le tue "ragioni che mi piacciono" sono buone e corrette.

A proposito di problemi che hai con quello. Bene, la dichiarazione in avanti non è un santo graal. Puoi semplicemente progettare il tuo codice per evitare dipendenze a più livelli.

Puoi spostare typedef fuori dalla classe ma Class :: ptr è molto più carino di ClassPtr che non lo faccio. È come con gli spazi dei nomi come per me: le cose rimangono connesse nell'ambito.

A volte l'ho fatto

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

E può essere predefinito per tutte le classi di dominio e con alcune specializzazioni per alcune classi.


3

L'STL fa sempre questo tipo di cose: i typedef fanno parte dell'interfaccia per molte classi nell'STL.

reference
iterator
size_type
value_type
etc...

sono tutti i typedef che fanno parte dell'interfaccia per varie classi di template STL.


Vero, e sospetto che sia qui che l'ho raccolto. Sembra che questi sarebbero un po 'più facili da giustificare però? Non posso fare a meno di vedere i typedef all'interno di un modello di classe come più affini alle variabili, se ti capita di pensare lungo la linea della "meta-programmazione".
Will Baker,

3

Un altro voto per questa è una buona idea. Ho iniziato a farlo quando ho scritto una simulazione che doveva essere efficiente, sia nel tempo che nello spazio. Tutti i tipi di valore avevano un typedef Ptr che era iniziato come un puntatore condiviso boost. Ho quindi fatto un po 'di profilazione e ho modificato alcuni di essi per aumentare il puntatore invadente senza dover modificare il codice in cui sono stati utilizzati questi oggetti.

Nota che funziona solo quando sai dove verranno usate le classi e che tutti gli usi hanno gli stessi requisiti. Non lo userei nel codice della biblioteca, ad esempio, perché non puoi sapere quando scrivi la biblioteca nel contesto in cui verrà usata.


3

Attualmente sto lavorando al codice, che utilizza intensamente questo tipo di typedef. Fin qui va bene.

Ma ho notato che ci sono spesso typedef iterativi, le definizioni sono suddivise in diverse classi e non si sa mai con che tipo di oggetto si abbia a che fare. Il mio compito è di riassumere le dimensioni di alcune complesse strutture di dati nascoste dietro questi tipi di errore, quindi non posso fare affidamento su interfacce esistenti. In combinazione con tre o sei livelli di spazi dei nomi nidificati, diventa confuso.

Quindi, prima di usarli, ci sono alcuni punti da considerare

  • Qualcun altro ha bisogno di questi typedef? La classe è molto usata da altre classi?
  • Accorcio l'utilizzo o nascondo la classe? (In caso di nascondimento potresti anche pensare a interfacce.)
  • Altre persone lavorano con il codice? Come lo fanno? Penseranno che è più facile o diventeranno confusi?

1

Consiglio di spostare quei typedef fuori dalla classe. In questo modo, rimuovi la dipendenza diretta dal puntatore condiviso e dalle classi vettoriali e puoi includerli solo quando necessario. A meno che tu non stia usando questi tipi nella tua implementazione di classe, ritengo che non dovrebbero essere typedef interni.

I motivi che ti piacciono sono ancora abbinati, poiché sono risolti dal tipo aliasing tramite typedef, non dichiarandoli all'interno della tua classe.


Sarebbe inquinare lo spazio dei nomi anonimo con i typedef, vero ?! Il problema con typedef è che nasconde il tipo effettivo, che può causare conflitti quando incluso in / da più moduli, che sono difficili da trovare / risolvere. È buona norma contenerli negli spazi dei nomi o all'interno delle classi.
Indy9000,

3
I conflitti di nomi e l'inquadramento anonimo dello spazio dei nomi hanno poco a che fare con il mantenimento di un nome di battesimo all'interno di una classe o all'esterno. Puoi avere un nome in conflitto con la tua classe, non con i tuoi typedef. Quindi, per evitare l'inquinamento dei nomi, usa gli spazi dei nomi. Dichiara la tua classe e i relativi typedef in uno spazio dei nomi.
Cătălin Pitiș,

Un altro argomento per mettere il typedef all'interno di una classe è l'uso di funzioni templatised. Quando, ad esempio, una funzione riceve un tipo di contenitore sconosciuto (vettore o elenco) contenente un tipo di stringa sconosciuto (stringa o la propria variante conforme alla stringa). l'unico modo per capire il tipo di payload del contenitore è con il typedef 'value_type' che fa parte della definizione della classe del contenitore.
Marius,

1

Quando il typedef viene utilizzato solo all'interno della classe stessa (ovvero viene dichiarato privato), penso che sia una buona idea. Tuttavia, per le ragioni esatte che fornisci, non lo userei se i typedef dovessero essere conosciuti al di fuori della classe. In tal caso, consiglio di spostarli fuori dalla classe.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.