Inicializando um std :: map estático <int, int> em C ++

447

Qual é a maneira correta de inicializar um mapa estático? Precisamos de uma função estática que a inicialize?

Nithin
fonte

Respostas:

619

Usando C ++ 11:

#include <map>
using namespace std;

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

Usando o 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
fonte
115
Toda vez que vejo algo assim feito com C ++, penso em todo o código de modelo horrendo que deve estar por trás dele. Bom exemplo!
Greg Hewgill 26/09/08
34
A beleza de todo o código de modelo horrendo que implementa esses utilitários é que ele é perfeitamente encapsulado em uma biblioteca e o usuário final raramente precisa lidar com a complexidade.
9788 Steve Guidi
45
@QBziZ: Se sua empresa recusar o uso do Boost por não ser "padrão o suficiente", pergunto-me que biblioteca C ++ seria "suficientemente padrão". O Boost é o companheiro padrão do codificador C ++.
precisa saber é o seguinte
47
Meu problema com o Boost (aqui e em outros lugares) é que você pode continuar sem ele (neste caso, com C ++ 11 ou antes do C ++ 11 com uma função ). O Boost adiciona uma sobrecarga significativa no tempo de compilação, tinha vários arquivos para estacionar no seu repositório (e ter que copiar em torno de / zip / extract, se você estiver criando um arquivo morto). É por isso que tento não usá-lo. Sei que você pode escolher quais arquivos incluir / não incluir, mas geralmente não deseja se preocupar com as dependências cruzadas do Boost, para copiar apenas a coisa toda.
bobobobo
7
Meu problema com o Boost é que ele geralmente possui várias novas dependências de biblioteca, o que geralmente significa MAIS pacotes que precisam ser instalados para funcionar corretamente. Já precisamos do libstdc ++. Por exemplo, a biblioteca Boost ASIO, requer pelo menos 2 novas bibliotecas (provavelmente mais) que precisam ser instaladas. O C ++ 11/14 torna muito mais fácil não precisar do Boost.
Rahly 31/05
135

A melhor maneira é usar uma função:

#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
fonte
18
Por que esse é o 'melhor'? Por que, por exemplo, é melhor que a resposta do @ Dreamer?
Marquês de Lorne
6
Eu acho que é "melhor" porque é realmente simples e não depende de outras estruturas existentes (como o Boost :: Assign ou uma reimplementação dele). E comparado à resposta do @ Dreamer, evito criar uma estrutura inteira apenas para inicializar um mapa ...
PierreBdR
3
Observe que há um perigo aqui . externAs variáveis ​​não terão seus valores corretos neste "antes do construtor principal em tempo de execução" se o compilador apenas viu a externdeclaração, mas ainda não executou a definição de variável real .
bobobobo
5
Não, o perigo é que não há nada dizendo em que ordem as variáveis ​​estáticas devem ser inicializadas (pelo menos nas unidades de compilação). Mas este não é um problema vinculado a esta pergunta. Este é um problema geral com variáveis ​​estáticas.
PierreBdR
5
sem aumento E sem C ++ 11 => +1. Observe que a função pode ser usada para inicializar a const map<int,int> m = create_map()(e, portanto, inicialize os membros const de uma classe na lista de inicialização:struct MyClass {const map<int, int> m; MyClass(); }; MyClass::MyClass() : m(create_map())
ribamar 13/15
115

Não é uma questão complicada fazer algo semelhante para impulsionar. Aqui está uma classe com apenas três funções, incluindo o construtor, para replicar o que o impulso fez (quase).

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

O código acima funciona melhor para a inicialização de variáveis ​​globais ou membros estáticos de uma classe que precisa ser inicializada e você não tem idéia de quando é usado primeiro, mas deseja garantir que os valores estejam disponíveis nele.

Se você diz, você precisa inserir elementos em um std :: map existente ... aqui está outra classe para você.

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

Veja em ação com o GCC 4.7.2 aqui: http://ideone.com/3uYJiH

############### TUDO ABAIXO ESTE É OBSOLETO #################

EDIT : A map_add_valuesclasse abaixo, que foi a solução original que eu sugeri, falharia quando se trata do GCC 4.5+. Veja o código acima para saber como adicionar valores ao 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> meu_map;
// Posteriormente em algum lugar ao longo do código
map_add_values ​​<int, int> (meu_map) (1,2) (3,4) (5,6);

NOTA: Anteriormente, usei a operator []para adicionar os valores reais. Isso não é possível, como comentado por dalle.

##################### FIM DA SEÇÃO OBSOLETA ######################

Vite Falcon
fonte
3
Estou usando seu primeiro exemplo como <int, string> para ligar números de erro (de um enum) a mensagens - está funcionando como um encanto - obrigado.
Slashmais 22/09/10
1
operator[]leva apenas um único argumento.
dalle
1
@dalle: Boa captura! Por alguma razão, pensei que operadores sobrecarregados [] pudessem aceitar mais.
Vite Falcon
2
Esta é uma resposta fantástica. É uma pena que o OP nunca tenha escolhido um. Você merece mega adereços.
Thomas Thorogood
o map_add_values ​​não funciona no gcc, que se queixa: 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

Aqui está outra maneira que usa o construtor de dados de 2 elementos. Nenhuma função é necessária para inicializá-lo. Não há código de terceiros (Boost), funções ou objetos estáticos, truques, apenas C ++ simples:

#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 escrevi esta resposta, o C ++ 11 está fora. Agora você pode inicializar diretamente os contêineres STL usando o novo recurso de lista de inicializadores:

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

Por exemplo:

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

Se o mapa for um membro de dados de uma classe, você poderá inicializá-lo diretamente no cabeçalho da seguinte maneira (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
fonte
24

Eu envolvia o mapa dentro de um objeto estático e colocava o código de inicialização do mapa no construtor desse objeto. Dessa forma, você tem certeza de que o mapa é criado antes da execução do código de inicialização.

Drealmer
fonte
1
Estou com você nessa. É também um pouco mais rápido :)
QBziZ
2
Um pouco mais rápido do que o que? Uma estática global com um inicializador? Não, não é (lembre-se do RVO).
Pavel Minaev 13/11/2009
7
Boa resposta. Ficaria feliz se eu ver o código de exemplo real
Sungguk Lim
18

Só queria compartilhar uma solução C ++ 98 pura:

#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;
user3826594
fonte
2
isso não funciona para o objeto sem construtor padrão, inserir método deve ser preferido IMHO
Alessandro Teruzzi
16

Podes tentar:

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
fonte
1
Você não pode usar listas de inicializadores com tipos não agregados antes do C ++ 11; nesse caso, você também pode usar a sintaxe mais curta em {1, 2}vez de std::pair<int, int>(1, 2).
Ferruccio
9

É semelhante a PierreBdR, sem copiar o 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
fonte
12
Provavelmente não teria sido copiado de qualquer maneira.
GManNickG 13/11/2009
2
mas esse mapa não poderia ser const estático, poderia?
Xmoex
6

Se você está preso ao C ++ 98 e não deseja usar o boost, aqui está a solução que eu uso quando preciso inicializar um 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
fonte
-4

Você tem algumas respostas muito boas aqui, mas eu sou para mim, parece um caso de "quando tudo que você sabe é um martelo" ...

A resposta mais simples de por que não existe uma maneira padrão de inicializar um mapa estático, não há uma boa razão para usar um mapa estático ...

Um mapa é uma estrutura projetada para pesquisa rápida, de um conjunto desconhecido de elementos. Se você conhece os elementos com antecedência, basta usar uma matriz C. Digite os valores de maneira ordenada ou execute uma classificação neles, se você não puder fazer isso. Você pode obter o desempenho do log (n) usando as funções stl :: para efetuar loop-up de entradas, lower_bound / upper_bound. Quando eu testei isso anteriormente, eles normalmente executam pelo menos 4 vezes mais rápido que um mapa.

As vantagens são muitas vezes ... - desempenho mais rápido (* 4, eu medi em muitos tipos de CPU, é sempre em torno de 4) - depuração mais simples. É mais fácil ver o que está acontecendo com um layout linear. - Implementações triviais de operações de cópia, caso isso seja necessário. - Ele não aloca memória no tempo de execução, portanto, nunca lançará uma exceção. - É uma interface padrão e, portanto, é muito fácil compartilhar entre DLLs ou idiomas, etc.

Eu poderia continuar, mas se você quiser mais, por que não olhar para os muitos blogs da Stroustrup sobre o assunto.

user2185945
fonte
8
O desempenho não é o único motivo para usar um mapa. Por exemplo, há muitos casos em que você deseja vincular valores (por exemplo, um código de erro com uma mensagem de erro) e um mapa torna o uso e o acesso relativamente simples. Mas um link para essas entradas do blog pode ser interessante, talvez eu esteja fazendo algo errado.
perfil
5
Uma matriz é muito mais fácil e tem desempenho superior, se você puder usá-la. Mas se os índices (chaves) não forem contíguos e com espaçamento amplo, você precisará de um mapa.
Karlu
1
A maptambém é uma forma útil para representar uma função parcial (função no sentido matemático; mas também, mais ou menos, no sentido de programação). Uma matriz não faz isso. Você não pode, por exemplo, pesquisar dados de uma matriz usando uma string.
einpoklum
3
Sua resposta não tenta responder à pergunta válida e, em vez disso, especula as limitações da linguagem, propõe soluções para diferentes problemas, portanto, com voto negativo. Um cenário real - mapeamento (contínuo ou não) de códigos de erro da biblioteca para sequências de texto. Com a matriz, o tempo de pesquisa é O (n), que pode ser melhorado pelo mapeamento estático para O (log (n)).
Tosha 22/01
2
Se, de fato, "não existe uma boa razão para usar um mapa estático ...", é muito estranho que a sintaxe (listas de inicializadores) que os tornam fáceis de usar tenha sido adicionada no C ++ 11.
Ellisbben