Verwenden von char * als Schlüssel in std :: map

81

Ich versuche herauszufinden, warum der folgende Code nicht funktioniert, und ich gehe davon aus, dass es sich um ein Problem bei der Verwendung von char * als Schlüsseltyp handelt. Ich bin mir jedoch nicht sicher, wie ich ihn beheben kann oder warum er auftritt. Alle anderen Funktionen, die ich (im HL2 SDK) verwende char*, werden std::stringviele unnötige Komplikationen verursachen.

std::map<char*, int> g_PlayerNames;

int PlayerManager::CreateFakePlayer()
{
    FakePlayer *player = new FakePlayer();
    int index = g_FakePlayers.AddToTail(player);

    bool foundName = false;

    // Iterate through Player Names and find an Unused one
    for(std::map<char*,int>::iterator it = g_PlayerNames.begin(); it != g_PlayerNames.end(); ++it)
    {
        if(it->second == NAME_AVAILABLE)
        {
            // We found an Available Name. Mark as Unavailable and move it to the end of the list
            foundName = true;
            g_FakePlayers.Element(index)->name = it->first;

            g_PlayerNames.insert(std::pair<char*, int>(it->first, NAME_UNAVAILABLE));
            g_PlayerNames.erase(it); // Remove name since we added it to the end of the list

            break;
        }
    }

    // If we can't find a usable name, just user 'player'
    if(!foundName)
    {
        g_FakePlayers.Element(index)->name = "player";
    }

    g_FakePlayers.Element(index)->connectTime = time(NULL);
    g_FakePlayers.Element(index)->score = 0;

    return index;
}
Josh Renwald
quelle
14
Manchmal tut es zuerst weh, das Richtige zu tun. Ändern Sie Ihren Code, um ihn std:stringeinmal zu verwenden , und freuen Sie sich danach.
Björn Pollex
1
Welche Art von Komplikationen? Es gibt eine implizite Konvertierung von char * nach std :: string.
vierundvierzig
1
Sie dürfen nicht char*als Kartenschlüssel verwenden. Siehe meine Antwort warum.
sbi
Dies scheint eine unnötige Komplikation durch verursacht wird nicht verwendet std::string.
Pedro d'Aquino
Ich verstehe nicht, dass die Karte nicht wissen muss, ob ein Schlüssel gleich ist, um einen Binärschlüssel zu verwenden, anstatt zu wissen, dass ein Schlüssel einen Wert hat, der kleiner als ein anderer ist.
CodeMinion

Antworten:

140

Sie müssen der Karte einen Vergleichsfunktor zuweisen, andernfalls wird der Zeiger verglichen, nicht die nullterminierte Zeichenfolge, auf die er zeigt. Im Allgemeinen ist dies immer dann der Fall, wenn Sie möchten, dass Ihr Kartenschlüssel ein Zeiger ist.

Zum Beispiel:

struct cmp_str
{
   bool operator()(char const *a, char const *b) const
   {
      return std::strcmp(a, b) < 0;
   }
};

map<char *, int, cmp_str> BlahBlah;
GWW
quelle
2
Eigentlich kann er nur &std::strcmpden dritten Vorlagenparameter übergeben
Armen Tsirunyan
23
Nein, strcmpgibt eine positive, nullte oder negative Ganzzahl zurück. Der Kartenfunktor muss true auf less-than und false zurückgeben, andernfalls.
Aschepler
4
@Armen: Ich denke nicht, dass es funktioniert, da der 3. Vorlagenparameter so etwas wie f(a,b) = a<bnicht erwartet f(a,b) = (-1 if a<b, 1 if a>b, 0 else).
Kennytm
28
Oh, sorry, mein Schlechtes, habe vor dem Posten nicht nachgedacht. Lass den Kommentar dort bleiben und beschäme meine Vorfahren :)
Armen Tsirunyan
2
Wie ich getestet habe, muss es mit einem const nach dem bool-Operator () (char const * a, char const * b) funktionieren, wie dem bool-Operator () (char const * a, char const * b) const {blabla
ethanjyx
45

Sie können es nur verwenden, char*wenn Sie absolut 100% sicher sind, dass Sie mit genau denselben Zeigern und nicht mit Zeichenfolgen auf die Karte zugreifen .

Beispiel:

char *s1; // pointing to a string "hello" stored memory location #12
char *s2; // pointing to a string "hello" stored memory location #20

Wenn Sie mit auf die Karte zugreifen, erhalten s1Sie einen anderen Ort als mit s2.

Pablo Santa Cruz
quelle
5
Es sei denn, Sie definieren Ihren eigenen Komparator, wie in der akzeptierten Antwort erläutert.
Lukas Kalinski
23

Zwei Zeichenfolgen im C-Stil können den gleichen Inhalt haben, sich jedoch an unterschiedlichen Adressen befinden. Und das mapvergleicht die Zeiger, nicht den Inhalt.

Die Kosten für die Konvertierung in sind std::map<std::string, int>möglicherweise nicht so hoch wie Sie denken.

Aber wenn Sie wirklich const char*als Kartenschlüssel verwenden müssen, versuchen Sie:

#include <functional>
#include <cstring>
struct StrCompare : public std::binary_function<const char*, const char*, bool> {
public:
    bool operator() (const char* str1, const char* str2) const
    { return std::strcmp(str1, str2) < 0; }
};

typedef std::map<const char*, int, StrCompare> NameMap;
NameMap g_PlayerNames;
aschepler
quelle
Danke für die Information. Aufgrund meiner Erfahrung empfehle ich dringend, in std :: string zu konvertieren.
user2867288
8

Sie können damit arbeiten std::map<const char*, int>, dürfen jedoch keine Nicht- constZeiger verwenden (beachten Sie die constfür den Schlüssel hinzugefügten ), da Sie diese Zeichenfolgen nicht ändern dürfen, während die Zuordnung sie als Schlüssel bezeichnet. (Während eine Karte ihre Schlüssel schützt, indem sie sie erstellt const, wird dadurch nur der Zeiger und nicht die Zeichenfolge, auf die sie zeigt, festgelegt.)

Aber warum benutzt du nicht einfach std::map<std::string, int>? Es funktioniert sofort ohne Kopfschmerzen.

sbi
quelle
8

Sie vergleichen mit a char *mit einer Zeichenfolge. Sie sind nicht gleich.

A char *ist ein Zeiger auf ein Zeichen. Letztendlich ist es ein ganzzahliger Typ, dessen Wert als gültige Adresse für a interpretiert wird char.

Eine Zeichenfolge ist eine Zeichenfolge.

Der Container funktioniert ordnungsgemäß, jedoch als Container für Paare, in denen der Schlüssel a char *und der Wert a ist int.

Daniel Daranas
quelle
1
Es ist nicht erforderlich, dass ein Zeiger eine lange Ganzzahl ist. Es gibt Plattformen (wie win64, falls Sie jemals davon gehört haben :-)), auf denen eine lange Ganzzahl kleiner als ein Zeiger ist, und ich glaube, es gibt auch dunkelere Plattformen, auf denen Zeiger und Ganzzahlen in verschiedene Register geladen und in unterschiedlich behandelt werden andere Möglichkeiten. C ++ erfordert nur, dass Zeiger in einen ganzzahligen Typ und zurück konvertierbar sind. Beachten Sie, dass dies nicht bedeutet, dass Sie eine ausreichend kleine Ganzzahl in einen Zeiger umwandeln können, sondern nur diejenigen, die Sie beim Konvertieren eines Zeigers erhalten haben.
Christopher Creutzig
@ChristopherCreutzig, danke für deinen Kommentar. Ich habe meine Antwort entsprechend bearbeitet.
Daniel Daranas
2

Wie die anderen sagen, sollten Sie in diesem Fall wahrscheinlich std :: string anstelle eines char * verwenden, obwohl mit einem Zeiger als Schlüssel im Prinzip nichts falsch ist, wenn dies wirklich erforderlich ist.

Ich denke, ein weiterer Grund, warum dieser Code nicht funktioniert, ist, dass Sie, sobald Sie einen verfügbaren Eintrag in der Karte gefunden haben, versuchen, ihn mit demselben Schlüssel (dem Zeichen *) wieder in die Karte einzufügen. Da dieser Schlüssel bereits in Ihrer Karte vorhanden ist, schlägt das Einfügen fehl. Der Standard für map :: insert () definiert dieses Verhalten ... Wenn der Schlüsselwert vorhanden ist, schlägt die Einfügung fehl und der zugeordnete Wert bleibt unverändert. Dann wird es trotzdem gelöscht. Sie müssen es zuerst löschen und dann wieder einfügen.

Auch wenn Sie das Zeichen * in einen std :: string ändern, bleibt dieses Problem bestehen.

Ich weiß, dass dieser Thread ziemlich alt ist und Sie haben inzwischen alles behoben, aber ich habe niemanden gesehen, der diesen Punkt so formuliert hat, um zukünftigen Zuschauern willen, denen ich antworte.

Toby Mitchell
quelle
0

Es fiel mir schwer, das Zeichen * als Zuordnungsschlüssel zu verwenden, wenn ich versuche, ein Element in mehreren Quelldateien zu finden. Es funktioniert einwandfrei, wenn alle Zugriffe / Suchen in derselben Quelldatei erfolgen, in die die Elemente eingefügt werden. Wenn ich jedoch versuche, mit find in einer anderen Datei auf das Element zuzugreifen, kann ich das Element nicht abrufen, das sich definitiv in der Karte befindet.

Es stellt sich heraus, dass der Grund dafür ist, wie Plabo betonte, dass die Zeiger (jede Kompilierungseinheit hat ihre eigene Konstante char *) NICHT identisch sind, wenn in einer anderen CPP-Datei darauf zugegriffen wird.

Jie Xu
quelle
-5

Es gibt kein Problem auf Tastentyp so lange zu verwenden , wie es Vergleich unterstützt ( <, >, ==) und Zuordnung.

Ein Punkt, der erwähnt werden sollte - berücksichtigen Sie, dass Sie eine Vorlagenklasse verwenden . Als Ergebnis generiert der Compiler zwei verschiedene Instanziierungen für char*und int*. Während der tatsächliche Code von beiden praktisch identisch sein wird.

Daher würde ich in Betracht ziehen, a void*als Schlüsseltyp zu verwenden und dann nach Bedarf zu gießen. Das ist meine Meinung.

valdo
quelle
8
Der Schlüssel muss nur unterstützt werden <. Aber es muss auf eine Weise umgesetzt werden, die hilfreich ist. Es ist nicht mit Zeigern. Moderne Compiler falten identische Vorlageninstanzen. (Ich weiß sicher, dass VC dies tut.) Ich würde es niemals verwenden void*, es sei denn, die Messung hat gezeigt, dass dies viele Probleme löst. Das Aufgeben der Typensicherheit sollte niemals vorzeitig erfolgen.
sbi