2010-11-11 30 views
61

Estoy tratando de averiguar por qué el siguiente código no funciona, y supongo que es un problema con el uso de char * como tipo de clave, sin embargo, no estoy seguro cómo puedo resolverlo o por qué está ocurriendo. Todas las otras funciones que uso (en el SDK HL2) usan char*, por lo que usar std::string va a causar muchas complicaciones innecesarias.Usando char * como clave en std :: map

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; 
} 
+10

A veces, hacer lo correcto duele al principio. Cambie su código para usar 'std: string' una vez, y sea feliz después. –

+1

¿qué tipo de complicaciones? hay una conversión implícita de char * a std :: string. – tenfour

+0

No debe usar 'char *' como una clave de mapa. Ver [mi respuesta] (http://stackoverflow.com/questions/4157687/using-char-as-a-key-in-stdmap/4157811#4157811) por qué. – sbi

Respuesta

108

Debe asignar un functor de comparación al mapa; de lo contrario, compara el puntero char * y no la cadena. En general, este es el caso cada vez que desee que su clave de mapa sea un puntero.

es decir.

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

map<char *, int, cmp_str> BlahBlah; 

EDIT: Acutally ignore mi edición, el functor es más fácil de usar.

+2

en realidad él puede simplemente pasar el '& std :: strcmp' como el tercer parámetro de plantilla –

+20

No,' strcmp' devuelve un entero positivo, cero o negativo. El functor del mapa debe devolver verdadero en menos que y falso en caso contrario. – aschepler

+4

@Armen: no creo que funcione, ya que el tercer parámetro de plantilla espera algo como 'f (a, b) = a b, 0 más) '. – kennytm

41

No se puede utilizar char* menos que esté absolutamente seguro al 100% que se va a acceder al mapa con los mismos punteros exactas, no cadenas.

Ejemplo:

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

Si accede mapa con s1 obtendrá un lugar diferente de acceder a él con s2.

+3

Muy buena explicación. –

8

Estás comparando el uso de un char * con una cuerda. Ellos no son los mismos.

A char * es un puntero a un char. En definitiva, es un tipo entero cuyo valor se interpreta como una dirección válida para char.

Una secuencia es una cadena.

El contenedor funciona correctamente, pero como un contenedor para pares en el que la clave es char * y el valor es int.

+1

No es obligatorio que un puntero sea un entero largo. Hay plataformas (como win64, si alguna vez has oído hablar de ella :-)) donde un entero largo es más pequeño que un puntero, y creo que también hay plataformas más oscuras donde los punteros y enteros se cargan en diferentes registros y se tratan de manera diferente en otras maneras. C++ solo requiere que los punteros sean convertibles en algún tipo integral y viceversa; tenga en cuenta que esto no implica que pueda convertir cualquier entero suficientemente pequeño en puntero, solo los que obtenga al convertir un puntero. –

+0

@ChristopherCreutzig, gracias por su comentario. Edité mi respuesta en consecuencia. –

21

Dos cadenas estilo C pueden tener el mismo contenido pero estar en diferentes direcciones. Y que map compara los punteros, no los contenidos.

El costo de la conversión a std::map<std::string, int> puede no ser tanto como usted cree.

Pero si realmente es necesario utilizar const char* como claves mapa, trate de:

#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; 
+0

Gracias por la información. En función de mi experiencia, recomiendo convertir a std :: string. – user2867288

-5

No hay ningún problema para utilizar cualquier tipo de clave, siempre que soporta la comparación (<, >, ==) y asignación.

Un punto que debe mencionarse: tenga en cuenta que está utilizando una plantilla clase. Como resultado, el compilador generará dos instancias diferentes para char* y int*. Mientras que el código real de ambos será prácticamente idéntico.

Por lo tanto, consideraría utilizar un void* como tipo de llave, y luego fundir según sea necesario. Esta es mi opinión.

+6

La clave solo necesita ser compatible con '<'. Pero necesita implementar eso de una manera que sea útil. No es con punteros. Los compiladores modernos doblarán instancias de plantilla idénticas. (Sé con certeza que VC hace esto.) Nunca usaría 'void *', a menos que la medición mostrara esto para resolver muchos problemas. __El abandono de la seguridad de tipo nunca debe hacerse prematuramente .__ – sbi

8

lo puede conseguir trabajar con std::map<const char*, int>, pero no se debe utilizar no const punteros (nótese el const añadido para la llave), porque no se debe cambiar esas cadenas, mientras que el mapa se refiere a ellos como claves. (Aunque un mapa protege sus claves haciéndolos const, esto sólo constify el puntero , no la cuerda que apunta.)

Pero ¿por qué no simplemente usar std::map<std::string, int>? Funciona de la caja sin dolores de cabeza.

2

Como dicen los demás, probablemente debería usar std :: string en lugar de un char * en este caso, aunque no hay nada de malo en principio con un puntero como clave si eso es lo que realmente se necesita.

Creo que otro motivo por el que este código no funciona es porque una vez que encuentras una entrada disponible en el mapa, intentas volver a insertarla en el mapa con la misma clave (el char *). Como esa clave ya existe en su mapa, la inserción fallará. El estándar para map :: insert() define este comportamiento ... si el valor de la clave existe, la inserción falla y el valor mapeado permanece sin cambios. Luego se borra de todos modos. Tendría que eliminarlo primero y luego reinsertarlo.

Incluso si cambia el char * a std :: string este problema se mantendrá.

Sé que este hilo es bastante antiguo y lo ha arreglado todo por ahora, pero no vi a nadie mencionar este punto por el bien de los futuros espectadores que estoy respondiendo.

0

Tuve un momento difícil utilizando el char * como la clave del mapa cuando trato de encontrar un elemento en varios archivos de origen. Funciona bien cuando todo el acceso/búsqueda dentro del mismo archivo fuente donde se insertan los elementos. Sin embargo, cuando intento acceder al elemento usando find en otro archivo, no puedo obtener el elemento que definitivamente está dentro del mapa.

Resulta que la razón es como Plabo señalado, los punteros (cada unidad de compilación tiene su propia char constante *) NO son los mismos cuando se accede a ellos en otro archivo cpp.

Cuestiones relacionadas