2009-04-17 15 views
148

Qualcosa mi sono trovato a fare spesso ultimamente sta dichiarando typedef rilevanti per una particolare classe all'interno di quella classe, vale a direErrori di battitura interni in C++ - stile buono o cattivo stile?

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

// 
// ... 
// 
}; 

Questi tipi sono poi utilizzati in altre parti del codice:

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

lorems.push_back(lorem); 

motivi che mi piace iT:

  • si 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 è intesa come riferimento conteggiata tramite boost::shared_ptr e memorizzata in un vettore.
  • Consente di cambiare l'implementazione, ad esempio se Lorem dovesse essere modificato per essere considerato conteggio intrusivo (tramite boost::intrusive_ptr) in una fase successiva, ciò avrebbe un impatto minimo sul codice.
  • Penso che sembri "più carino" ed è discutibilmente più facile da leggere.

ragioni che non mi piace:

  • a volte ci sono problemi con le dipendenze - se si desidera incorporare, diciamo, un Lorem::vector all'interno di un'altra classe, ma solo bisogno (o vogliono) per inoltrare declare Lorem (al contrario di introdurre una dipendenza dal suo file di intestazione), si finisce per dover usare i tipi espliciti (es. boost::shared_ptr<Lorem> piuttosto che 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 codifica, quindi sarebbe bello avere qualche altra opinione su di esso in modo da poter sezionare un po 'il mio pensiero.

risposta

131

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

+0

Questo è un buon punto, mi chiedo che sembrare "più carina" è stato il mio subconscio sottolineando delicatamente che lo scopo limitato è una cosa * buona *. Mi chiedo però, il fatto che lo STL lo usi prevalentemente nei modelli di classe ne fa un uso leggermente diverso? È più difficile giustificare in una classe 'concreta'? –

+1

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

2

Mi raccomando di spostare quei typedef fuori dalla classe. In questo modo, rimuovi la dipendenza diretta dal puntatore condiviso e dalle classi vettoriali e puoi includerle solo quando necessario. A meno che non stiate usando questi tipi nella vostra implementazione di classe, ritengo che non dovrebbero essere dei typedef interiori.

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

+0

Questo avrebbe poluto lo spazio dei nomi anonimo con i typedef, vero ?! Il problema con typedef è che nasconde il tipo effettivo, che può causare conflitti se incluso in/da più moduli, che sono difficili da trovare/correggere. È una buona pratica per contenere questi in spazi dei nomi o all'interno di classi. – Indy9000

+3

I conflitti di nome e l'inquinamento dello spazio dei nomi anonimo hanno poco a che fare con il mantenimento di un nome tipografico 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 del nome, usa gli spazi dei nomi. Dichiara la tua classe e i typedef correlati in uno spazio dei nomi. –

+0

Un altro argomento per inserire il typedef in una classe è l'uso di funzioni templatizzate. Quando, per esempio, una funzione riceve un tipo di contenitore sconosciuto (vettore o lista) 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 typedef 'value_type' che fa parte della definizione della classe del contenitore. – Marius

0

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

7

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

A proposito di problemi con questo. Bene, la dichiarazione in avanti non è un Santo Graal. Puoi semplicemente progettare il tuo codice per evitare dipendenze a più livelli.

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

A volte ho fatto

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

e può essere di default per tutte le classi di dominio e con una certa specializzazione per quelli determinati.

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 typedef che fanno parte dell'interfaccia per varie classi template STL.

+0

Vero, e sospetto che sia qui che l'ho preso per primo. Sembra che questi sarebbero un po 'più semplici da giustificare? Non posso fare a meno di visualizzare typedef all'interno di un modello di classe come più simile alle variabili, se vi capita di pensare lungo la linea di "meta-programmazione". –

9

I typedef sono quelli che sono policy based design and traits basati su C++, quindi La potenza della programmazione generica in C++ deriva da typedef stessi.

+0

Non molto più, dato che abbiamo 'decltype' ora ... – Deduplicator

3

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

Si noti che questo funziona solo quando si sa dove verranno utilizzate le classi e che tutti gli usi hanno gli stessi requisiti. Non lo userei nel codice della libreria, ad esempio, perché non puoi sapere quando scrivi la libreria nel contesto in cui verrà utilizzata.

9

Serve come una dichiarazione di intenti - nell'esempio di cui sopra, la classe Lorem è destinato ad essere riferimento contato via boost :: shared_ptr e conservati in un vettore.

Questo è esattamente ciò che fa non fare.

Se vedo "Foo :: Ptr" nel codice, non ho assolutamente idea se sia un shared_ptr o un Foo * (STL ha :: pointer typedefs che sono T *, ricorda) o qualsiasi altra cosa. Esp. se è un puntatore condiviso, non fornisco affatto un typedef, ma mantieni esplicitamente il shared_ptr nel codice.

In realtà, non utilizzo quasi mai typedef al di fuori del modello Metaprogramming.

Lo STL fa questo tipo di cose tutto il tempo

Il design STL con i concetti definiti in termini di funzioni membro e typedef nidificati è un cul-de-sac storico, moderne librerie di template uso gratuito classi di funzioni e tratti (cfr Boost.Graph), poiché non escludono i tipi built-in dalla modellazione del concetto e perché rende più semplice l'adattamento di tipi che non erano progettati con i concetti delle librerie di template specificati.

Non utilizzare l'AWL come motivo per commettere gli stessi errori.

+0

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

+0

@Dennis: In C++, la convenienza dovrebbe essere sul lato del/user/di una libreria, non sul lato del suo/author /: l'utente desidera un'interfaccia uniforme per un tratto, e solo una classe di tratto può darlo , a causa dei motivi sopra indicati). Ma anche come autore di 'Alloc', non è esattamente più difficile specializzare' std :: allocator_traits <> 'per il suo nuovo tipo piuttosto che aggiungere i typedef necessari. Ho anche modificato la risposta, perché la mia risposta completa non rientrava in un commento. –

+0

Ma * è * sul lato dell'utente. Come * utente * di 'allocator_traits' che tenta di creare un allocatore personalizzato, non devo preoccuparmi dei quindici membri della classe dei caratteri ... tutto quello che devo fare è dire" typedef Blah value_type; "e fornire il funzioni membro appropriate e il valore predefinito 'allocator_traits' determinerà il resto. Inoltre, guarda il tuo esempio di Boost.Graph. Sì, fa un uso pesante dei tratti di classe ...ma l'implementazione predefinita di 'graph_traits ' richiede semplicemente 'G' per i propri typedef interni. –

1

Attualmente sto lavorando al codice, che utilizza intensivamente questo tipo di typedef. Finora va bene.

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

Quindi, prima del loro utilizzo, ci sono alcuni punti da considerare

  • Qualcun altro hanno bisogno di questi typedef? La classe è usata molto dalle altre classi?
  • Accorcio l'utilizzo o nascondo la classe? (In caso di occultamento potresti anche pensare alle interfacce.)
  • Altre persone stanno lavorando con il codice? Come lo fanno? Penseranno che sia più facile o diventeranno confusi?
Problemi correlati