2010-03-17 18 views
29

Nella mia applicazione C++, ho alcuni valori che fungono da codici per rappresentare altri valori. Per tradurre i codici, ho discusso tra l'utilizzo di un'istruzione switch o una mappa stl. L'interruttore sarebbe simile a questa:Istruzione switch C++ lungo o cercare con una mappa?

int code; 
int value; 
switch(code) 
{ 
case 1: 
    value = 10; 
    break; 
case 2: 
    value = 15; 
    break; 
} 

La mappa sarebbe un stl::map<int, int> e traduzione sarebbe una semplice ricerca con il codice utilizzato come valore chiave.

Quale è migliore/più efficiente/più pulito/accettato? Perché?

+1

'valore = 5 * (codice + 1);' – kennytm

+5

@KennyTM - Eccellente. Tranne che non ci sono i valori attuali ... – Rachel

+0

+1 per una domanda interessante. –

risposta

7

Personalmente, vorrei usare la mappa, poiché il suo uso implica una ricerca dei dati - l'utilizzo di un interruttore di solito indica una differenza nel comportamento del programma. Inoltre, la modifica della mappatura dei dati è più semplice con una mappa che con un interruttore.

Se la prestazione è un problema reale, la profilatura è l'unico modo per ottenere una risposta utilizzabile. Un interruttore potrebbe non essere più veloce se le errate previsioni dei rami accadono abbastanza spesso.

altro approccio per pensare a questo è se non avrebbe più senso per combinare il codice e il valore associato in una datastructure, specialmente se l'intervallo di codici e valori è statico:

struct Code { int code; int value; }; 

Code c = ... 

std::cout << "Code " << c.code << ", value " << c.value << std::end; 
+0

Mi piace il punto che hai fatto sulle mappe che implicano la ricerca dei dati e gli switch che indicano la differenza in comportamento. – Rachel

0

Dico map se tutto quello che state facendo è assegnare un valore. La mia ragione è che è estensibile senza modificare il codice, che non è la tua istruzione switch.

btw, che ne dici di un enum?

+0

Impossibile eseguire enum per tradurre gli inte. – Rachel

7

Se i codici sono abbastanza contigue e il loro permesso di gamma che si sarebbe molto meglio che con matrice semplice vecchio stile, qualcosa di simile a

int lookup[] = {-1, 10, 15, -1 222}; 

quindi l'istruzione switch può essere riscritta nel modo più semplice

value = lookup [codice];

tutte le altre opzioni introducono costi aggiuntivi in ​​una certa misura.

+0

Non sono contigui. Questa è parte del problema. Sto cercando di tradurre in modo efficiente un elenco casuale di interi in un altro elenco casuale di interi. – Rachel

+0

+1. A volte viene chiamato un approccio "table" –

+0

@Rachel: entrambi gli elenchi casuali sono noti in fase di compilazione? Se sì e la discontinuità non è grande, la tabella di ricerca è ancora un buon modo. – kennytm

0

Penso che il codice generato della struttura di switch-case possa crescere abbastanza grande, se la quantità di "codici" diventa grande, nel qual caso penso che la stl :: map sia più appropriata.

2

L'istruzione switch sarebbe più veloce, ma se questo non è il collo di bottiglia delle prestazioni delle applicazioni, non ci si dovrebbe preoccupare di questo.

Scegli cosa rende il tuo codice più facile da mantenere a lungo termine.

Il tuo campione è troppo corto per effettuare qualsiasi chiamata significativa a tale riguardo.

4

Si piuttosto dipende da cosa sono i codici e quanti ce ne sono. I buoni compilatori hanno vari trucchi che usano per ottimizzare le dichiarazioni di switch, alcune delle quali non verranno utilizzate con istruzioni if ​​/ then diritte. La maggior parte sono abbastanza brillanti da eseguire semplici calcoli matematici o utilizzare tabelle di ricerca/salto per il caso 0, 1, 2 o caso 3, 6, 9 per esempio.

Naturalmente alcuni non lo fanno, e molti sono facilmente sventati da insiemi di valori insoliti o irregolari. Inoltre, se il codice per la gestione di diversi casi sembra molto simile, il taglio e l'incolla possono causare problemi di manutenzione.Se si dispone di molti codici, ma possono essere divisi in modo algoritmico in gruppi, si potrebbe prendere in considerazione diversi/nidificato istruzioni switch, per esempio, piuttosto che:

switch (code) { 
    case 0x0001: ... 
    case 0x0002: ... 
    ... 
    case 0x8001: ... 
    case 0x8002: ... 
    ... 
} 

È possibile utilizzare:

if (code & 0x8000) { 
    code &= ~0x8000; 
    switch (code) { 
     case 0x0001: ... // actually 0x8001 
     case 0x0002: ... // actually 0x8002 
     ... 
    } 
} 
else { 
    switch (code) { 
     case 0x0001: ... 
     case 0x0002: ... 
     ... 
    } 
} 

Molti interpreti di lingua decodificano opcodes in questo modo, dal momento che un opcode a byte singolo può contenere informazioni aggiuntive impacchettate in vari bit e trascrivere tutte le possibili combinazioni e i loro gestori sarebbe ripetitivo e fragile. D'altro canto, un eccessivo bit di manipolazione può vanificare qualsiasi ottimizzazione da parte del compilatore ed essere controproducente.

A meno che non si sia certi che si tratti di un vero e proprio collo di bottiglia delle prestazioni, eviterei l'ottimizzazione prematura: farlo in qualsiasi modo vi sembra ragionevolmente robusto e rapido da implementare. Come e se la tua applicazione sta funzionando troppo lentamente, profila il profilo e ottimizza di conseguenza.

-1
  • Leggere i numeri interi in un array/vettore
  • ordinare l'array/vettore
  • uso bsearch sulla matrice sottostante
+0

Perché non usare solo una std :: map? Le garanzie di prestazione sono le stesse e il codice è più semplice. – Peter

+0

Stampa footer di memoria più piccola == località migliore == prestazioni migliori – EvilTeach

2

io ho un debole per Lookup tabelle me stesso, perché l'interruttore insolitamente lungo le affermazioni mi sembrano confondere una separazione tra codice e dati. Penso anche che le tabelle si prestino meglio a cambiamenti e manutenzione futuri.

Tutto IMHO, ovviamente.

1

Se è possibile utilizzare tr1, è possibile utilizzare unordered_map per eseguire l'hash dei valori (anche gli hashing possono essere molto veloci), il che dovrebbe rendere la maggior parte delle ricerche a tempo costante.

Tuttavia, a meno che non si disponga di dati di profilo per indicare che si tratta di un collo di bottiglia nel programma, codificarlo nell'approccio che ha più senso dal punto di vista del progetto.

2

Suggerisco di utilizzare una tabella di coppie statiche, costanti. Questa è un'altra forma di tabella guardare in alto:

struct Table_Entry 
{ 
    int code; 
    int value; 
}; 

static const Table_Entry lookup_table[] = 
{ 
    {1, 10}, 
    {2, 15}, 
    {3, 13}, 
}; 

static const unsigned int NUM_TABLE_ENTRIES = 
    sizeof(lookup_table)/sizeof(lookup_table[0]); 

Un vantaggio di questo è che la tabella è generato al momento della compilazione, a differenza di un std::map che deve essere inizializzato in fase di esecuzione. Se le quantità sono grandi, è possibile utilizzare std::lower_bound per trovare la voce, a condizione che la tabella sia ordinata.

Un altro vantaggio è che questa tecnica è basata sui dati. I dati possono cambiare senza modifiche al motore di ricerca. Le modifiche al codice o al processo possono richiedere seri test di regressione, ma le modifiche ai dati potrebbero non avvenire; YMMV.

Questo è simile a ciò che potrebbe generare un compilatore.

0

Normalmente suggerirei una mappa o array di ricerca o forse anche qualche mostro di ricerca ibrido (supponendo che tu stia ottimizzando la velocità o la dimensione del codice piuttosto che la leggibilità, ovviamente), ma vale la pena notare che le nuove versioni di GCC sono abbastanza intelligenti da sostituire le assegnazioni di interruttori/casi come questo alle tabelle di ricerca. Anche se questo non è così grande per chiavi totalmente sparse può essere se le chiavi sono in gruppi come: 0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 193, 194 , 195, 196, 197, 198 ...

Ovviamente, se è possibile dedicare qualche operazione aritmetica alla conversione, anche meglio (di solito).Non trascurare mai il cambio di direzione, lo scambio di endianness o la matematica tradizionale quando fai qualcosa del genere.

Problemi correlati