Inicializando un std :: map <int, int> estático en C ++

448

¿Cuál es la forma correcta de inicializar un mapa estático? ¿Necesitamos una función estática que lo inicialice?

Nithin
fuente

Respuestas:

619

Usando C ++ 11:

#include <map>
using namespace std;

map<int, char> m = {{1, 'a'}, {3, 'b'}, {5, 'c'}, {7, 'd'}};

Usando Boost.Assign :

#include <map>
#include "boost/assign.hpp"
using namespace std;
using namespace boost::assign;

map<int, char> m = map_list_of (1, 'a') (3, 'b') (5, 'c') (7, 'd');
Ferruccio
fuente
115
Cada vez que veo algo así hecho con C ++, pienso en todo el horrendo código de plantilla que debe estar detrás de él. ¡Buen ejemplo!
Greg Hewgill
34
La belleza de todo el horrendo código de plantilla que implementa estas utilidades es que está perfectamente encapsulado en una biblioteca y el usuario final rara vez necesita lidiar con la complejidad.
Steve Guidi
45
@QBziZ: Si su empresa rechaza usar Boost debido a que no es "lo suficientemente estándar", me pregunto qué biblioteca de C ++ sería "lo suficientemente estándar". Boost es el compañero estándar para el codificador C ++.
DevSolar
47
Mi problema con Boost (aquí y en otros lugares) es que a menudo puedes seguir sin él (en este caso con C ++ 11 o antes de C ++ 11 con una función ). Boost agrega una sobrecarga de tiempo de compilación significativa, tenía toneladas de archivos para estacionar en su repositorio (y para tener que copiar / zip / extraer si está haciendo un archivo). Esa es la razón por la que trato de no usarlo. Sé que puedes elegir qué archivos incluir / no incluir, pero generalmente no quieres tener que preocuparte por las dependencias cruzadas de Boost consigo mismo, así que simplemente copia todo.
bobobobo 01 de
77
Mi problema con Boost es que a menudo tiene varias dependencias de biblioteca nuevas, lo que generalmente significa MÁS paquetes que deben instalarse para funcionar correctamente. Ya necesitamos libstdc ++. Por ejemplo, la biblioteca Boost ASIO requiere al menos 2 bibliotecas nuevas (probablemente más) que deben instalarse. C ++ 11/14 hace que sea mucho más fácil no necesitar Boost.
Rahly
135

La mejor manera es usar una función:

#include <map>

using namespace std;

map<int,int> create_map()
{
  map<int,int> m;
  m[1] = 2;
  m[3] = 4;
  m[5] = 6;
  return m;
}

map<int,int> m = create_map();
PierreBdR
fuente
18
¿Por qué es este el "mejor"? ¿Por qué, por ejemplo, es mejor que la respuesta de @ Dreamer?
Marqués de Lorne
66
Creo que es "mejor" porque es realmente simple y no depende de otras estructuras existentes (como Boost :: Assign o una reimplementación de la misma). Y en comparación con la respuesta de @ Dreamer, bueno, evito crear una estructura completa solo para inicializar un mapa ...
PierreBdR
3
Tenga en cuenta que hay un peligro aquí . externlas variables no tendrán sus valores correctos en este "antes del constructor principal en tiempo de ejecución" si el compilador solo vio la externdeclaración, pero aún no se ha encontrado con la definición de variable real .
bobobobo 01 de
55
No, el peligro es que no hay nada que diga en qué orden deben inicializarse las variables estáticas (al menos en las unidades de compilación). Pero este no es un problema relacionado con esta pregunta. Este es un problema general con variables estáticas.
PierreBdR
55
sin impulso Y sin C ++ 11 => +1. Observe que la función se puede usar para inicializar a const map<int,int> m = create_map()(y, por lo tanto, inicializar miembros constantes de una clase en la lista de inicialización:struct MyClass {const map<int, int> m; MyClass(); }; MyClass::MyClass() : m(create_map())
ribamar
115

No es un problema complicado hacer algo similar para impulsar. Aquí hay una clase con solo tres funciones, incluido el constructor, para replicar lo que hizo (casi).

template <typename T, typename U>
class create_map
{
private:
    std::map<T, U> m_map;
public:
    create_map(const T& key, const U& val)
    {
        m_map[key] = val;
    }

    create_map<T, U>& operator()(const T& key, const U& val)
    {
        m_map[key] = val;
        return *this;
    }

    operator std::map<T, U>()
    {
        return m_map;
    }
};

Uso:

std :: map mymap = create_map <int, int> (1,2) (3,4) (5,6);

El código anterior funciona mejor para la inicialización de variables globales o miembros estáticos de una clase que debe inicializarse y no tiene idea de cuándo se usa primero, pero desea asegurarse de que los valores estén disponibles en ella.

Si dice, debe insertar elementos en un std :: map existente ... aquí hay otra clase para usted.

template <typename MapType>
class map_add_values {
private:
    MapType mMap;
public:
    typedef typename MapType::key_type KeyType;
    typedef typename MapType::mapped_type MappedType;

    map_add_values(const KeyType& key, const MappedType& val)
    {
        mMap[key] = val;
    }

    map_add_values& operator()(const KeyType& key, const MappedType& val) {
        mMap[key] = val;
        return *this;
    }

    void to (MapType& map) {
        map.insert(mMap.begin(), mMap.end());
    }
};

Uso:

typedef std::map<int, int> Int2IntMap;
Int2IntMap testMap;
map_add_values<Int2IntMap>(1,2)(3,4)(5,6).to(testMap);

Véalo en acción con GCC 4.7.2 aquí: http://ideone.com/3uYJiH

############### TODO A CONTINUACIÓN ES OBSOLETO #################

EDITAR : La map_add_valuesclase a continuación, que era la solución original que sugerí, fallaría cuando se trata de GCC 4.5+. Consulte el código anterior para ver cómo agregar valores al mapa existente.


template<typename T, typename U>
class map_add_values
{
private:
    std::map<T,U>& m_map;
public:
    map_add_values(std::map<T, U>& _map):m_map(_map){}
    map_add_values& operator()(const T& _key, const U& _val)
    {
        m_map[key] = val;
        return *this;
    }
};

Uso:

std :: map <int, int> my_map;
// Más tarde en algún lugar del código
map_add_values ​​<int, int> (my_map) (1,2) (3,4) (5,6);

NOTA: Anteriormente utilicé un operator []para agregar los valores reales. Esto no es posible como lo comentó dalle.

##################### FIN DE LA SECCIÓN OBSOLETA #####################

Vite Falcon
fuente
3
Estoy usando su primera muestra como <int, string> para vincular números de error (de una enumeración) con mensajes; funciona de maravilla, gracias.
slashmais
1
operator[]solo toma un solo argumento.
dalle
1
@dalle: ¡Buena captura! Por alguna razón, pensé que los operadores sobrecargados [] podrían aceptar más.
Vite Falcon
2
Esta es una respuesta fantástica. Es una pena que el OP nunca haya seleccionado uno. Te mereces mega accesorios.
Thomas Thorogood
el map_add_values ​​no funciona en gcc, que se queja: error: conflicting declaration ‘map_add_values<int, int> my_map’ error: ‘my_map’ has a previous declaration as ‘std::map<int, int> my_map’
Martin Wang
42

Aquí hay otra forma que usa el constructor de datos de 2 elementos. No se necesitan funciones para inicializarlo. No hay código de terceros (Boost), no hay funciones u objetos estáticos, no hay trucos, solo C ++ simple:

#include <map>
#include <string>

typedef std::map<std::string, int> MyMap;

const MyMap::value_type rawData[] = {
   MyMap::value_type("hello", 42),
   MyMap::value_type("world", 88),
};
const int numElems = sizeof rawData / sizeof rawData[0];
MyMap myMap(rawData, rawData + numElems);

Desde que escribí esta respuesta, C ++ 11 está fuera. Ahora puede inicializar directamente los contenedores STL utilizando la nueva función de lista de inicializadores:

const MyMap myMap = { {"hello", 42}, {"world", 88} };
Brian Neal
fuente
25

Por ejemplo:

const std::map<LogLevel, const char*> g_log_levels_dsc =
{
    { LogLevel::Disabled, "[---]" },
    { LogLevel::Info,     "[inf]" },
    { LogLevel::Warning,  "[wrn]" },
    { LogLevel::Error,    "[err]" },
    { LogLevel::Debug,    "[dbg]" }
};

Si map es un miembro de datos de una clase, puede inicializarlo directamente en el encabezado de la siguiente manera (desde C ++ 17):

// Example

template<>
class StringConverter<CacheMode> final
{
public:
    static auto convert(CacheMode mode) -> const std::string&
    {
        // validate...
        return s_modes.at(mode);
    }

private:
    static inline const std::map<CacheMode, std::string> s_modes =
        {
            { CacheMode::All, "All" },
            { CacheMode::Selective, "Selective" },
            { CacheMode::None, "None" }
            // etc
        };
}; 
isnullxbh
fuente
24

Envolvería el mapa dentro de un objeto estático y pondría el código de inicialización del mapa en el constructor de este objeto, de esta manera está seguro de que el mapa se crea antes de que se ejecute el código de inicialización.

Drealmer
fuente
1
Estoy contigo en este caso. También es un poco más rápido :)
QBziZ
2
¿Un poco más rápido que qué? ¿Una estática global con un inicializador? No, no lo es (recuerda sobre RVO).
Pavel Minaev el
77
Buena respuesta. Sería feliz si veo el código de ejemplo real
Sungguk Lim
18

Solo quería compartir una solución pura de C ++ 98:

#include <map>

std::map<std::string, std::string> aka;

struct akaInit
{
    akaInit()
    {
        aka[ "George" ] = "John";
        aka[ "Joe" ] = "Al";
        aka[ "Phil" ] = "Sue";
        aka[ "Smitty" ] = "Yando";
    }
} AkaInit;
usuario3826594
fuente
2
esto no funciona para el objeto sin el constructor predeterminado, el método de inserción debe ser preferido en mi humilde opinión
Alessandro Teruzzi
16

Puedes probar:

std::map <int, int> mymap = 
{
        std::pair <int, int> (1, 1),
        std::pair <int, int> (2, 2),
        std::pair <int, int> (2, 2)
};
Dmitry Oberemchenko
fuente
1
No puede usar listas de inicializador con tipos no agregados antes de C ++ 11, en cuyo caso también puede usar la sintaxis más corta en {1, 2}lugar de std::pair<int, int>(1, 2).
Ferruccio
9

Esto es similar a PierreBdR, sin copiar el mapa.

#include <map>

using namespace std;

bool create_map(map<int,int> &m)
{
  m[1] = 2;
  m[3] = 4;
  m[5] = 6;
  return true;
}

static map<int,int> m;
static bool _dummy = create_map (m);
eduffy
fuente
12
Probablemente no hubiera sido copiado de todos modos.
GManNickG
2
pero de esta manera el mapa no podría ser constante, ¿verdad?
xmoex
6

Si está atascado con C ++ 98 y no desea usar boost, aquí está la solución que uso cuando necesito inicializar un mapa estático:

typedef std::pair< int, char > elemPair_t;
elemPair_t elemPairs[] = 
{
    elemPair_t( 1, 'a'), 
    elemPair_t( 3, 'b' ), 
    elemPair_t( 5, 'c' ), 
    elemPair_t( 7, 'd' )
};

const std::map< int, char > myMap( &elemPairs[ 0 ], &elemPairs[ sizeof( elemPairs ) / sizeof( elemPairs[ 0 ] ) ] );
Emanuele Benedetti
fuente
-4

Tienes algunas muy buenas respuestas aquí, pero yo soy para mí, parece un caso de "cuando todo lo que sabes es un martillo" ...

La respuesta más simple de por qué no hay una forma estándar de inicializar un mapa estático, es que no hay una buena razón para usar un mapa estático ...

Un mapa es una estructura diseñada para una búsqueda rápida, de un conjunto desconocido de elementos. Si conoce los elementos de antemano, simplemente use una matriz C. Ingrese los valores de manera ordenada, o ejecute sort sobre ellos, si no puede hacer esto. A continuación, puede obtener el rendimiento de log (n) utilizando las funciones stl :: para enlazar las entradas, lower_bound / upper_bound. Cuando he probado esto anteriormente, normalmente funcionan al menos 4 veces más rápido que un mapa.

Las ventajas son muchas veces ... - rendimiento más rápido (* 4, he medido en muchos tipos de CPU, siempre es alrededor de 4) - depuración más simple. Es más fácil ver qué sucede con un diseño lineal. - Implementaciones triviales de operaciones de copia, en caso de que sea necesario. - No asigna memoria en tiempo de ejecución, por lo que nunca arrojará una excepción. - Es una interfaz estándar, por lo que es muy fácil compartirla, archivos DLL o idiomas, etc.

Podría seguir, pero si quieres más, ¿por qué no miras los numerosos blogs de Stroustrup sobre el tema?

usuario2185945
fuente
8
El rendimiento no es la única razón para usar un mapa. Por ejemplo, hay muchos casos en los que desea vincular valores (por ejemplo, un código de error con un mensaje de error), y un mapa hace que el uso y el acceso sean relativamente simples. Pero un enlace a estas entradas de blog puede ser interesante, tal vez estoy haciendo algo mal.
MatthiasB
55
Una matriz es mucho más fácil y tiene un mayor rendimiento si puede usarla. Pero si los índices (claves) no son contiguos y están muy separados, necesita un mapa.
KarlU
1
A maptambién es una forma útil para representar una función parcial (función en el sentido matemático; pero también, más o menos, en el sentido de la programación). Una matriz no hace eso. No puede, por ejemplo, buscar datos de una matriz utilizando una cadena.
einpoklum
3
Su respuesta no intenta responder a la pregunta válida y, en cambio, especula sobre las limitaciones del lenguaje, propone soluciones a diferentes problemas y, por lo tanto, rechaza. Un escenario real: asignación de códigos de error de biblioteca (continuos o no) a cadenas de texto. Con la matriz, el tiempo de búsqueda es O (n), que puede mejorarse mediante mapeo estático a O (log (n)).
Tosha
2
Si de hecho "no hay una buena razón para usar un mapa estático ...", entonces es muy extraño que la sintaxis (listas de inicializadores) que los hace fáciles de usar se haya agregado en C ++ 11.
ellisbben 01 de