Como inicializar um mapa const estático privado em C ++?

108

Eu preciso apenas de um dicionário ou array associativo string=> int.

Existe um mapa de tipos C ++ para este caso.

Mas eu preciso de apenas um mapa para todas as instâncias (-> estático) e este mapa não pode ser alterado (-> const);

Eu descobri essa maneira com a biblioteca boost

 std::map<int, char> example = 
      boost::assign::map_list_of(1, 'a') (2, 'b') (3, 'c');

Existe outra solução sem esta lib? Eu tentei algo assim, mas sempre há alguns problemas com a inicialização do mapa.

class myClass{
private:
    static map<int,int> create_map()
        {
          map<int,int> m;
          m[1] = 2;
          m[3] = 4;
          m[5] = 6;
          return m;
        }
    static map<int,int> myMap =  create_map();

};
Meloun
fonte
1
Quais são os problemas aos quais você se refere? Você está tentando usar este mapa de outra variável / constante estática global?
Péter Török
Isso não é uma string de matriz associativa => int, você está mapeando um int para um char. v = k + 'a' - 1.
Johnsyweb

Respostas:

107
#include <map>
using namespace std;

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

};

const map<int,int> A:: myMap =  A::create_map();

int main() {
}

fonte
3
+1 para simplificar, é claro que usar um Boost.Assigndesign semelhante é muito legal também :)
Matthieu M.
5
+1, obrigado. Nota: Tive que colocar a linha de inicialização em meu arquivo de implementação; deixá-lo no arquivo de cabeçalho gerou erros devido a várias definições (o código de inicialização seria executado sempre que o cabeçalho fosse incluído em algum lugar).
System.Cats.Lol
1
Com g ++ v4.7.3, Isso compila, até que eu adicionar cout << A::myMap[1];em main(). Isso dá um erro. O erro não ocorre se eu remover os constqualificadores, então eu acho que o mapa operator[]não consegue lidar com const map, pelo menos, não na implementação g ++ da biblioteca C ++.
Craig McQueen
2
O erro é:const_map.cpp:22:23: error: passing ‘const std::map<int, int>’ as ‘this’ argument of ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const key_type&) [with _Key = int; _Tp = int; _Compare = std::less<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = int; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = int]’ discards qualifiers [-fpermissive]
Craig McQueen
4
Na verdade, o operador do mapa [] não pode operar em um mapa const porque esse operador cria a entrada referenciada se ela não existir (uma vez que retorna uma referência ao valor mapeado). C ++ 11 introduziu o método at (chave KeyValT) que permite acessar o item com uma determinada chave, lançando uma exceção se ele não existir. ( en.cppreference.com/w/cpp/container/map/at ) Este método funcionará em instâncias const, mas não pode ser usado para inserir um elemento em uma instância não const (como faz o operador []).
mbargiel
108

O padrão C ++ 11 introduziu a inicialização uniforme, o que torna isso muito mais simples se o seu compilador for compatível:

//myClass.hpp
class myClass {
  private:
    static map<int,int> myMap;
};


//myClass.cpp
map<int,int> myClass::myMap = {
   {1, 2},
   {3, 4},
   {5, 6}
};

Veja também esta seção do Professional C ++ , em unordered_maps.

David C. Bishop
fonte
Precisamos do sinal de igual no arquivo cpp?
phoad
@phoad: O sinal de igual é supérfluo.
Jinxed em
Obrigado por mostrar o uso. Foi muito útil entender como modificar as variáveis ​​estáticas.
User9102d82
12

Eu fiz isso! :)

Funciona bem sem C ++ 11

class MyClass {
    typedef std::map<std::string, int> MyMap;

    struct T {
        const char* Name;
        int Num;

        operator MyMap::value_type() const {
            return std::pair<std::string, int>(Name, Num);
        }
    };

    static const T MapPairs[];
    static const MyMap TheMap;
};

const MyClass::T MyClass::MapPairs[] = {
    { "Jan", 1 }, { "Feb", 2 }, { "Mar", 3 }
};

const MyClass::MyMap MyClass::TheMap(MapPairs, MapPairs + 3);
user2622030
fonte
11

Se você achar boost::assign::map_list_ofútil, mas não puder usá-lo por algum motivo, poderá escrever o seu próprio :

template<class K, class V>
struct map_list_of_type {
  typedef std::map<K, V> Map;
  Map data;
  map_list_of_type(K k, V v) { data[k] = v; }
  map_list_of_type& operator()(K k, V v) { data[k] = v; return *this; }
  operator Map const&() const { return data; }
};
template<class K, class V>
map_list_of_type<K, V> my_map_list_of(K k, V v) {
  return map_list_of_type<K, V>(k, v);
}

int main() {
  std::map<int, char> example = 
    my_map_list_of(1, 'a') (2, 'b') (3, 'c');
  cout << example << '\n';
}

É útil saber como essas coisas funcionam, especialmente quando são tão curtas, mas neste caso eu usaria uma função:

a.hpp

struct A {
  static map<int, int> const m;
};

a.cpp

namespace {
map<int,int> create_map() {
  map<int, int> m;
  m[1] = 2; // etc.
  return m;
}
}

map<int, int> const A::m = create_map();
Yu Hao
fonte
6

Uma abordagem diferente para o problema:

struct A {
    static const map<int, string> * singleton_map() {
        static map<int, string>* m = NULL;
        if (!m) {
            m = new map<int, string>;
            m[42] = "42"
            // ... other initializations
        }
        return m;
    }

    // rest of the class
}

Isso é mais eficiente, pois não há cópia de um tipo da pilha para a pilha (incluindo construtor, destruidores em todos os elementos). Se isso é importante ou não, depende do seu caso de uso. Não importa com cordas! (mas você pode ou não achar esta versão "mais limpa")

ypnos
fonte
3
RVO elimina a cópia na minha resposta e no Neil.
6

Se o mapa deve conter apenas entradas conhecidas em tempo de compilação e as chaves do mapa são inteiros, você não precisa usar um mapa.

char get_value(int key)
{
    switch (key)
    {
        case 1:
            return 'a';
        case 2:
            return 'b';
        case 3:
            return 'c';
        default:
            // Do whatever is appropriate when the key is not valid
    }
}
Matthew T. Staebler
fonte
5
+1 para apontar que um mapa não é necessário, no entanto, você não pode iterar sobre isso
Viktor Sehr
4
Mas isso switché horrível. Porque não return key + 'a' - 1?
Johnsyweb
12
@Johnsyweb. Presumo que o mapeamento fornecido pelo autor da postagem original foi apresentado apenas como um exemplo e não indicativo do mapeamento real que ele possui. Portanto, eu também presumiria que return key + 'a' - 1isso não funcionaria para seu mapeamento real.
Matthew T. Staebler
3

Você pode tentar isto:

MyClass.h

class MyClass {
private:
    static const std::map<key, value> m_myMap; 
    static const std::map<key, value> createMyStaticConstantMap();
public:
    static std::map<key, value> getMyConstantStaticMap( return m_myMap );
}; //MyClass

MyClass.cpp

#include "MyClass.h"

const std::map<key, value> MyClass::m_myMap = MyClass::createMyStaticConstantMap();

const std::map<key, value> MyClass::createMyStaticConstantMap() {
    std::map<key, value> mMap;
    mMap.insert( std::make_pair( key1, value1 ) );
    mMap.insert( std::make_pair( key2, value2 ) );
    // ....
    mMap.insert( std::make_pair( lastKey, lastValue ) ); 
    return mMap;
} // createMyStaticConstantMap

Com essa implementação, o mapa estático constante de suas classes é um membro privado e pode ser acessado por outras classes usando um método get público. Caso contrário, uma vez que é constante e não pode mudar, você pode remover o método get público e mover a variável de mapa para a seção pública de classes. No entanto, eu deixaria o método createMap privado ou protegido se herança e / ou polimorfismo forem necessários. Aqui estão alguns exemplos de uso.

 std::map<key,value> m1 = MyClass::getMyMap();
 // then do work on m1 or
 unsigned index = some predetermined value
 MyClass::getMyMap().at( index ); // As long as index is valid this will 
 // retun map.second or map->second value so if in this case key is an
 // unsigned and value is a std::string then you could do
 std::cout << std::string( MyClass::getMyMap().at( some index that exists in map ) ); 
// and it will print out to the console the string locted in the map at this index. 
//You can do this before any class object is instantiated or declared. 

 //If you are using a pointer to your class such as:
 std::shared_ptr<MyClass> || std::unique_ptr<MyClass>
 // Then it would look like this:
 pMyClass->getMyMap().at( index ); // And Will do the same as above
 // Even if you have not yet called the std pointer's reset method on
 // this class object. 

 // This will only work on static methods only, and all data in static methods must be available first.

Eu havia editado minha postagem original, não havia nada de errado com o código original em que postei compilado, construído e executado corretamente, só que minha primeira versão que apresentei como resposta o mapa foi declarado como público e o mapa foi const, mas não estático.

Francis Cugler
fonte
2

Se você estiver usando um compilador que ainda não suporta a inicialização universal ou você tem reservas em usar Boost, outra alternativa possível seria a seguinte

std::map<int, int> m = [] () {
    std::pair<int,int> _m[] = {
        std::make_pair(1 , sizeof(2)),
        std::make_pair(3 , sizeof(4)),
        std::make_pair(5 , sizeof(6))};
    std::map<int, int> m;
    for (auto data: _m)
    {
        m[data.first] = data.second;
    }
    return m;
}();
Abhijit
fonte
0

Uma chamada de função não pode aparecer em uma expressão constante.

tente isto: (apenas um exemplo)

#include <map>
#include <iostream>

using std::map;
using std::cout;

class myClass{
 public:
 static map<int,int> create_map()
    {
      map<int,int> m;
      m[1] = 2;
      m[3] = 4;
      m[5] = 6;
      return m;
    }
 const static map<int,int> myMap;

};
const map<int,int>myClass::myMap =  create_map();

int main(){

   map<int,int> t=myClass::create_map();
   std::cout<<t[1]; //prints 2
}
Prasoon Saurav
fonte
6
Uma função pode certamente ser usada para inicializar um objeto const.
No código do OP static map<int,int> myMap = create_map();está incorreto.
Prasoon Saurav
3
O código da pergunta está errado, todos concordamos com isso, mas não tem nada a ver com 'expressões constantes', como você diz nesta resposta, mas sim com o fato de que você só pode inicializar membros estáticos constantes de uma classe no declaração se eles forem do tipo inteiro ou enum. Para todos os outros tipos, a inicialização deve ser feita na definição do membro e não na declaração.
David Rodríguez - dribeas
A resposta de Neil é compilada com g ++. Ainda assim, lembro-me de ter alguns problemas com essa abordagem em versões anteriores da cadeia de ferramentas GNU. Existe uma resposta certa universal?
Basilevs
1
@Prasoon: Não sei o que o compilador diz, mas o erro no código da pergunta é inicializar um atributo de membro constante do tipo de classe na declaração de classe, independentemente de a inicialização ser uma expressão constante ou não. Se você definir uma classe: struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td;ela não conseguirá compilar, mesmo se a inicialização for realizada com uma expressão constante ( 5). Ou seja, 'expressão constante' é irrelevante para a exatidão (ou falta dela) do código inicial.
David Rodríguez - dribeas
-2

Costumo usar esse padrão e recomendo que você também o use:

class MyMap : public std::map<int, int>
{
public:
    MyMap()
    {
        //either
        insert(make_pair(1, 2));
        insert(make_pair(3, 4));
        insert(make_pair(5, 6));
        //or
        (*this)[1] = 2;
        (*this)[3] = 4;
        (*this)[5] = 6;
    }
} const static my_map;

Claro que não é muito legível, mas sem outras bibliotecas, é o melhor que podemos fazer. Além disso, não haverá nenhuma operação redundante, como copiar de um mapa para outro, como em sua tentativa.

Isso é ainda mais útil dentro das funções: Em vez de:

void foo()
{
   static bool initComplete = false;
   static Map map;
   if (!initComplete)
   {
      initComplete = true;
      map= ...;
   }
}

Use o seguinte:

void bar()
{
    struct MyMap : Map
    {
      MyMap()
      {
         ...
      }
    } static mymap;
}

Não apenas você não precisa mais lidar com a variável booleana, você não terá uma variável global oculta que é verificada se o inicializador da variável estática dentro da função já foi chamado.

Pavel Chikulaev
fonte
6
A herança deve ser a ferramenta de último recurso, não a primeira.
Um compilador que oferece suporte a RVO elimina a cópia redundante com as versões de função. A semântica de movimentação C ++ 0x elimina o resto, assim que estiverem disponíveis. Em qualquer caso, duvido que esteja perto de ser um gargalo.
Roger, estou bem ciente do RVO, && e da semântica do movimento. Esta é uma solução por enquanto com uma quantidade mínima de código e entidades. Além disso, todos os recursos do C ++ 0x não ajudarão com o exemplo de objeto estático dentro de função, pois não temos permissão para definir funções dentro de funções.
Pavel Chikulaev