2010-02-22 17 views
24

Tengo un mapa que representa un objeto DB. Quiero obtener los valores de los conocidos 'de ellaidioma correcto para std :: string constants?

std::map<std::string, std::string> dbo; 
... 
std::string val = map["foo"]; 

todo bien, pero se me ocurre que "foo" se convierte en una cadena temporal en cada llamada. Seguramente sería mejor tener una std :: string constante (por supuesto, es probablemente una pequeña sobrecarga en comparación con el disco IO que acaba de obtener el objeto, pero sigue siendo una pregunta válida, creo). Entonces, ¿cuál es la expresión correcta para std :: string constants?

por ejemplo - que puede tener

const std::string FOO = "foo"; 

en un HDR, pero luego obtener múltiples copias

EDIT: Aún no hay respuestas ha dicho cómo declarar constantes std :: string. Ignora todo el problema del mapa, STL, etc. Una gran cantidad de código es muy std :: string oriented (la mía ciertamente lo es) y es natural querer constantes para ellos sin pagar una y otra vez por la asignación de memoria

EDIT2: sacó la pregunta secundaria respondida por PDF de Manuel, ejemplo adicional de mala expresión

EDIT3: Resumen de respuestas. Tenga en cuenta que no he incluido aquellos que sugirieron crear una nueva clase de cadena. Estoy decepcionado porque esperaba que hubiera algo simple que funcionaría solo en el archivo de encabezado (como const char * const). De todos modos

a) de Marca b

std::map<int, std::string> dict; 
const int FOO_IDX = 1; 
.... 
dict[FOO_IDX] = "foo"; 
.... 
std:string &val = dbo[dict[FOO_IDX]]; 

b) de Vlad

// str.h 
extern const std::string FOO; 
// str.cpp 
const std::string FOO = "foo"; 

c) de Roger P

// really you cant do it 

(b) parece el más cercano a lo que quería pero tiene un defecto fatal. No puedo tener un código de nivel de módulo estático que use estas cadenas ya que es posible que aún no se hayan construido. Pensé en (a) y, de hecho, utilizar un truco similar al serializar el objeto, enviar el índice en lugar de la cadena, pero parecía una gran cantidad de plomería para una solución de propósito general. Así que por desgracia (c) ha sufrido, no hay sencilla idioma const para std: cadena

+0

En la edición, traté de abordar eso ("no, así no es como std :: string funciona" y "aquí hay otra clase de cadenas"), pero tenga en cuenta que si simplemente desea almacenar const std :: strings en alguna parte como globales, solo tendrías que pagar por la asignación una vez (al inicio del programa) con una implementación ref-contada (que proporciona gcc y el estándar explícitamente permite). Solo asegúrese de que esos valores globales * const * eviten fácilmente todo tipo de errores lógicos. –

+0

, por lo que su respuesta fue: no es posible. No estaba claro. Si a nadie se le ocurre una mejor respuesta, entonces obtienes la marca – pm100

+0

. En realidad, me gusta la respuesta de Manuel actualmente. Disculpas por no haber sido lo suficientemente clara, por eso hice el comentario, pero también cogí otra opción por accidente. :) –

Respuesta

17

La copia y la falta de "cadena de optimización literal" se lo std :: trabajan cuerdas, y no se puede conseguir exactamente lo que' preguntando por En parte esto se debe a que los métodos virtuales y dtor fueron explícitamente evitados. La interfaz std :: string es plenty complicado sin ellos, de todos modos.

El estándar requiere una cierta interfaz para std :: string y std :: map, y esas interfaces no permiten la optimización que desea (como "consecuencia involuntaria" de sus otros requisitos, en lugar de explícitamente). Al menos, no lo permiten si realmente quiere seguir todos los detalles del estándar. Y realmente quieres eso, especialmente cuando es tan fácil usar una clase de cadena diferente para esta optimización específica.

Sin embargo, esa clase de cadena separada puede resolver estos "problemas" (como usted dijo, rara vez es un problema), pero desafortunadamente el mundo ya tiene number_of_programmers + 1. Incluso teniendo en cuenta la reinvención de la rueda, he encontrado que es útil tener una clase StaticString, que tiene un subconjunto de la interfaz de std :: string: usando begin/end, substr, find, etc.También impide la modificación (y se ajusta a los literales de cadena de esa manera), almacenando solo un puntero de char y un tamaño. Tiene que ser un poco cuidadoso que sólo ha inicializado con literales de cadena u otros datos "estáticos", pero eso es algo mitigada por la interfaz de construcción:

struct StaticString { 
    template<int N> 
    explicit StaticString(char (&data)[N]); // reference to char array 
    StaticString(StaticString const&); // copy ctor (which is very cheap) 

    static StaticString from_c_str(char const* c_str); // static factory function 
    // this only requires that c_str not change and outlive any uses of the 
    // resulting object(s), and since it must also be called explicitly, those 
    // requirements aren't hard to enforce; this is provided because it's explicit 
    // that strlen is used, and it is not embedded-'\0'-safe as the 
    // StaticString(char (&data)[N]) ctor is 

    operator char const*() const; // implicit conversion "operator" 
    // here the conversion is appropriate, even though I normally dislike these 

private: 
    StaticString(); // not defined 
}; 

Uso:

StaticString s ("abc"); 
assert(s != "123"); // overload operators for char* 
some_func(s); // implicit conversion 
some_func(StaticString("abc")); // temporary object initialized from literal 

Nota la principal ventaja de esta clase es explícitamente evitar copiar datos de cadena, por lo que el almacenamiento literal de cadena puede ser reutilizado. Hay un lugar especial en el ejecutable para estos datos, y generalmente está bien optimizado ya que data de los primeros días de C y más allá. De hecho, creo que esta clase está cerca de lo que los literales de cadena deberían haber sido en C++, si no fuera por el requisito de compatibilidad de C.

Por extensión, también podría escribir su propia clase de mapa si este es un escenario muy común para usted, y eso podría ser más fácil que cambiar los tipos de cadena.

+12

Si hay suficiente demanda, podría reinventar esta clase desde cero para poner SO en virtud de la licencia de código abierto de SO (normalmente soy muy cuidadoso al respecto). Digamos que si este comentario obtiene +10 votos, entonces lo haré. :) –

+1

@RogerPate estás en el gancho ahora –

6
  1. Es posible evitar la sobrecarga de crear un std::string cuando lo que quieres es una cadena constante. Pero necesitarás escribir una clase especial para eso porque no hay nada similar en el STL o en Boost. O una mejor alternativa es usar una clase como StringPiece de Chromium o StringRef de LLVM. Consulte esto related thread para obtener más información.

  2. Si decide quedarse con std::string (que probablemente lo hará) y luego otra buena opción es utilizar el contenedor MultiIndex Boost, que tiene la siguiente función (citando the docs):

    Boost MultiIndex [. ..] proporciona operaciones de búsqueda que aceptan las claves de búsqueda diferentes del tipo de clave del índice , que es una función especialmente útil cuando los objetos de tipo_clave son caros de crear.

Maps with Expensive Keys por Andrei Alexandrescu (C/C++ Diario de Usuarios, febrero de 2006) se relaciona con su problema y es una muy buena lectura.

+1

@ Manuel tienes razón, el pdf es una coincidencia exacta y una lectura interesante. Él no discute la pregunta constante tho – pm100

0

El problema es que std::map copia la clave y los valores en sus propias estructuras.

Puede tener un std::map<const char *, const char *>, pero debe proporcionar objetos funcionales (o funciones) para comparar la clave y los datos de valor, ya que esta galería de símbolos es para los punteros. De forma predeterminada, el map compararía los punteros y no los datos a los que apuntan los punteros.

La compensación es una copia única (std::string) contra el acceso a un comparador (const char *).

Otra alternativa es escribir su propia función map.

+0

En la primera línea, por supuesto, hoy en día no es tan malo, ya que tenemos semántica de movimiento. –

1

El idioma correcto es el que estás usando. 99.99% del tiempo no hay necesidad de preocuparse por la sobrecarga del constructor de std :: string.

¿Me pregunto si el constructor de std :: string podría convertirse en una función intrínseca por un compilador?Teóricamente podría ser posible, pero mi comentario anterior sería una explicación suficiente de por qué no ha sucedido.

+0

Mejor aún, solo haga que el optimizador sea lo suficientemente avanzado para descubrir cómo usar strlen en el ctor de cadena (el código se reduce al equivalente del de std :: string, o wcslen para wstring) un intrínseco, cuando se usa en una cadena literal. Más difícil, cierto, y no sé si alguno hace esto, pero también sería más beneficioso que std :: string. - Oh, tonto, eso es solo la mitad de la batalla, ya que debes copiar los datos aún. Hmm. –

+0

Sí, el problema consiste en reconocer que al constructor se le pasa una constante estática y configurar el puntero de almacenamiento interno sin copiar. La información de tipo estándar no va a ese nivel de detalle, por lo que no es posible prescindir de magia especial del compilador. –

1

Parece que ya sabe cuáles serán los literales de cadena en tiempo de ejecución, por lo que puede configurar una asignación interna entre los valores enumerados y una matriz de cadenas. Luego usaría la enumeración en lugar de un literal const char * real en su código.

enum ConstStrings 
{ 
    MAP_STRING, 
    FOO_STRING, 
    NUM_CONST_STRINGS 
}; 

std::string constStrings[NUM_CONST_STRINGS]; 

bool InitConstStrings() 
{ 
    constStrings[MAP_STRING] = "map"; 
    constStrings[FOO_STRING] = "foo"; 
} 

// Be careful if you need to use these strings prior to main being called. 
bool doInit = InitConstStrings(); 

const std::string& getString(ConstStrings whichString) 
{ 
    // Feel free to do range checking if you think people will lie to you about the parameter type. 
    return constStrings[whichString]; 
} 

Entonces usted diría map[getString(MAP_STRING)] o similar.

Como acotación al margen, también consideran que almacena el valor devuelto por la referencia constante en lugar de copia si no es necesario modificarlo:

const std::string& val = map["foo"]; 
+0

¿Por qué no usar simplemente una matriz de std :: strings? http://codepad.org/6b3JkcJj –

+1

@ pm100: Puede usar varios trucos de generación de código para "conocer" los datos por adelantado, de forma similar a cómo la biblioteca gettext i18n puede extraer cadenas; sin embargo, esto es mucho trabajo y puede simplemente poner manualmente las cadenas en un encabezado + implementación según sea necesario, ya que no es algo que deba hacer a menudo porque estamos hablando de constantes de cadenas estáticas (cadenas literales con azúcar sintáctico especial) , De Verdad). –

+0

@Roger Pate: en realidad, no había pensado en utilizar la plantilla de matriz, y también me gusta esa solución. No está cambiando la configuración con la frecuencia suficiente para que sea doloroso actualizar el parámetro N. –

9

Es muy sencillo: utilizar

extern const std::string FOO; 

en su cabecera, y

const std::string FOO("foo"); 

en el archivo correspondiente .cpp.

+0

¿Qué significa 'approriate .cp file'? – pm100

+2

Para cada encabezado (por ejemplo, 'foo.h') uno tiene un archivo' .cpp' apropiado ('foo.cpp' en nuestro caso). Los encabezados se pueden incluir muchas veces, por lo que generalmente contienen declaraciones de datos (sin embargo, con la excepción habitual de las clases de plantilla). El archivo '.cpp' contiene definiciones reales. – Vlad

2

En C++ 14 que puede hacer

const std::string FOO = "foo"s; 
+0

Esto no cambia nada funcional sobre cómo (o dónde, o cuántas veces) se inicializa la cadena ([ref] (http://en.cppreference.com/w/cpp/string/basic_string/operator%22% 22s)), por lo que no veo cómo es relevante para las preocupaciones reales del OP. –

0

creo que lo que estás buscando es 'impulso :: peso mosca < std :: string>'

esto es una referencia lógicamente const a un valor de cadena compartido. almacenamiento muy eficiente y alto rendimiento.

0

Mi solución (que tiene la ventaja de ser capaz de utilizar C++ 11 características que no estaban presentes cuando esta pregunta fue respondida con anterioridad):

#define INTERN(x) ([]() -> std::string const & { \ 
    static const std::string y = x; \ 
    return y; \ 
}()) 

my_map[INTERN("key")] = 5; 

Sí, es una macro y podría usar una mejor nombre

Cuestiones relacionadas