2010-03-17 9 views
29

In meiner C++ - Anwendung habe ich einige Werte, die als Codes fungieren, um andere Werte darzustellen. Um die Codes zu übersetzen, habe ich über die Verwendung einer switch-Anweisung oder einer stl-Map debattiert. Der Schalter würde wie folgt aussehen:C++ Lange Switch-Anweisung oder nachschlagen mit einer Karte?

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

Die Karte ein stl::map<int, int> und Übersetzung ein einfaches Nachschlagen mit dem Code würde als Schlüsselwert verwendet würde.

Welches ist besser/effizienter/sauberer/akzeptiert? Warum?

+1

'Wert = 5 * (Code + 1);' – kennytm

+5

@KennyTM - Ausgezeichnet. Außer es gibt nicht die tatsächlichen Werte ... – Rachel

+0

+1 für eine interessante Frage. –

Antwort

7

Persönlich würde ich die Karte verwenden, da ihre Verwendung eine Datensuche beinhaltet - die Verwendung eines Schalters zeigt normalerweise einen Unterschied im Programmverhalten an. Darüber hinaus ist das Ändern der Datenzuordnung mit einer Map einfacher als mit einem Switch.

Wenn Leistung ein echtes Problem ist, ist Profiling die einzige Möglichkeit, eine brauchbare Antwort zu erhalten. Ein Wechsel kann nicht schneller sein, wenn Verzweigungsfehlvorhersagen oft genug passieren.

Ein anderer Ansatz, um darüber nachzudenken, wenn es nicht mehr Sinn machen würde, den Code und den zugehörigen Wert in eine Datenstruktur zu kombinieren, vor allem, wenn der Bereich von Codes und Werten statisch ist:

struct Code { int code; int value; }; 

Code c = ... 

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

Ich mag den Punkt, den Sie über Karten impliziert Datensuche und Schalter, die Unterschiede in Verhalten. – Rachel

0

Ich sage map, wenn Sie nur einen Wert zuweisen. Mein Grund ist, dass es erweiterbar ist, ohne Code zu ändern, was Ihre switch-Anweisung nicht ist.

btw, wie wäre es mit einem enum?

+0

Es kann keine Aufzählung gemacht werden, um Ints in Ints zu übersetzen. – Rachel

7

Wenn Ihre Codes sind zusammenhängenden genug und ihre Reichweite erlauben Sie wäre viel besser, der mit altmodischen einfachen Array, so etwas wie

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

dann die switch-Anweisung kann so einfach wie

neu geschrieben werden Wert = Nachschlag [Code];

Alle anderen Optionen führen in gewissem Umfang zu zusätzlichen Kosten.

+0

Sie sind nicht zusammenhängend. Das ist ein Teil des Problems. Ich versuche, eine zufällige Liste von Ints effizient in eine andere, ziemlich zufällige Liste von Ints zu übersetzen. – Rachel

+0

+1. Das wird manchmal als "Tabellen" -Ansatz bezeichnet. –

+0

@Rachel: Sind beide Zufallslisten in der Kompilierzeit bekannt? Wenn ja und die Diskontinuität nicht groß ist, ist die Nachschlagetabelle immer noch ein guter Weg. – kennytm

0

Ich denke, der generierte Code der Switch-Case-Struktur kann ziemlich groß werden, wenn die Anzahl der "Codes" groß wird. In diesem Fall halte ich die stl :: map für besser geeignet.

2

Die switch-Anweisung wäre schneller, aber wenn dies nicht in Ihrem Anwendungsleistungsengpass liegt, sollten Sie sich nicht wirklich darum kümmern.

Wählen Sie, was Ihren Code auf lange Sicht einfacher zu warten macht.

Ihre Stichprobe ist zu kurz, um in dieser Hinsicht sinnvolle Aufrufe zu tätigen.

4

Es hängt vielmehr davon ab, was die Codes sind und wie viele es sind. Gute Compiler haben verschiedene Tricks, um Switch-Anweisungen zu optimieren, von denen einige nicht mit geraden if/then-Anweisungen verwendet werden. Die meisten sind hell genug, um einfache Mathematik zu machen oder Such-/Sprungtabellen für Fall 0, 1, 2 oder Fall 3, 6, 9 zum Beispiel zu verwenden.

Natürlich einige nicht, und viele werden leicht durch ungewöhnliche oder unregelmäßige Mengen von Werten vereitelt. Auch wenn der Code für die Bearbeitung mehrerer Fälle sehr ähnlich aussieht, kann das Ausschneiden und Einfügen zu Wartungsproblemen führen.Wenn Sie viele Codes haben, aber sie können algorithmisch in Gruppen unterteilt werden, könnten Sie zum Beispiel mehrere/geschachtelte Switch-Anweisungen, betrachten anstatt:

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

Sie verwenden könnten:

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

Viele Sprachdolmetscher dekodieren Opcodes auf diese Weise, da ein Ein-Byte-Opcode zusätzliche Information haben kann, die in verschiedene Bits gepackt ist, und alle möglichen Kombinationen transkribiert werden, und ihre Handler würden sich wiederholend und zerbrechlich sein. Auf der anderen Seite kann übermäßiges Bit-Mangling jegliche Optimierung durch den Compiler verhindern und kontraproduktiv sein.

Wenn Sie nicht sicher sind, dass dies ein echter Leistungsengpass ist, würde ich eine vorzeitige Optimierung vermeiden: Tun Sie es, wie es Ihnen als einigermaßen robust und schnell zu implementieren erscheint. Wenn Ihre Anwendung zu langsam läuft, profilieren Sie sie und optimieren Sie sie entsprechend.

-1
  • Lesen Sie die ganzen Zahlen in ein Array/Vektor
  • sortieren das Array/Vektor
  • Verwendung bsearch auf dem darunterliegenden Array
+0

Warum nicht einfach eine std :: map verwenden? Die Leistungsgarantien sind gleich und der Code ist einfacher. – Peter

+0

Kleinerer Speicherfußdruck == bessere Lokalität == bessere Leistung – EvilTeach

2

Ich bin Teil-Tabellen selbst nachzuschlagen, weil ungewöhnlich langen Schalter Aussagen scheinen mir eine Trennung von Code und Daten zu verwechseln. Ich denke auch, dass Tabellen sich für zukünftige Änderungen und Wartung besser eignen.

Alle IMHO, natürlich.

1

Wenn Sie tr1 verwenden können, können Sie unordered_map verwenden, um die Werte zu hashen (Hashing-Ints können auch sehr schnell sein), was die meisten Lookups konstant machen sollte.

Wenn Sie jedoch keine Profiling-Daten haben, um anzuzeigen, dass dies ein Engpass in Ihrem Programm ist, codieren Sie es in dem Ansatz, der aus Sicht des Entwurfs am sinnvollsten ist.

2

Ich empfehle, eine statische, konstante Tabelle von Paaren zu verwenden. Dies ist eine weitere Form der Nachschlagetabelle:

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]); 

Ein Vorteil hierbei ist, dass die Tabelle bei der Kompilierung erzeugt wird, im Gegensatz zu einem std::map, die während der Laufzeit initialisiert werden muss. Wenn die Mengen groß sind, können Sie std::lower_bound verwenden, um den Eintrag zu finden, sofern die Tabelle bestellt wurde.

Ein weiterer Vorteil ist diese Technik ist datengesteuert. Die Daten können sich ohne Änderungen an der Suchmaschine ändern. Änderungen am Code oder Prozess können schwerwiegende Regressionstests erfordern, Datenänderungen jedoch nicht; YMMV.

Dies ähnelt dem, was ein Compiler erzeugen könnte.

0

Normalerweise würde ich vorschlagen, eine Karte oder Lookup-Array oder vielleicht sogar ein Hybrid-Lookup-Monster (vorausgesetzt, dass Sie für die Geschwindigkeit oder Code-Größe statt natürlich Lesbarkeit zu optimieren), aber es ist bemerkenswert, dass die neuen Versionen von GCC sind schlau genug, um Switch/Case-Zuordnungen wie diese zu Lookup-Tabellen zu ersetzen. Während dies für total spärliche Schlüssel nicht so toll ist, kann es sein, wenn Schlüssel in Gruppen wie: 0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 193, 194 sind , 195, 196, 197, 198 ...

Natürlich, wenn Sie möglicherweise etwas arithmatic mögen, um die Umwandlung zu machen, noch besser (normalerweise).Übersehen Sie niemals das Verschieben, den Austausch von Endianness oder traditionelle Mathematik, wenn Sie so etwas tun.