Jak zainicjować prywatną statyczną mapę const w C ++?

108

Potrzebuję tylko słownika lub tablicy asocjacyjnej string=> int.

W tym przypadku istnieje mapa typów C ++.

Ale potrzebuję tylko jednej mapy dla wszystkich instancji (-> statyczna) i tej mapy nie można zmienić (-> stała);

Znalazłem ten sposób w bibliotece boost

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

Czy istnieje inne rozwiązanie bez tej biblioteki? Próbowałem czegoś takiego, ale zawsze pojawiają się problemy z inicjalizacją mapy.

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
źródło
1
Do jakich problemów się odnosisz? Czy próbujesz użyć tej mapy z innej globalnej zmiennej / stałej statycznej?
Péter Török,
To nie jest tablica asocjacyjna string => int, mapujesz int na znak. v = k + 'a' - 1.
Johnsyweb

Odpowiedzi:

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() {
}

źródło
3
+1 za prostotę, oczywiście użycie Boost.Assignpodobnego projektu też jest całkiem fajne :)
Matthieu M.
5
+1, dzięki. Uwaga: musiałem umieścić wiersz inicjalizacji w moim pliku implementacji; pozostawienie go w pliku nagłówkowym dawało mi błędy z powodu wielu definicji (kod inicjalizacyjny byłby uruchamiany za każdym razem, gdy nagłówek był gdzieś zawarty).
System.Cats.Lol
1
W g ++ v4.7.3 kompiluje się, dopóki nie dodam cout << A::myMap[1];do main(). Daje błąd. Błąd nie występuje, jeśli usunę constkwalifikatory, więc wydaje mi się , że mapy operator[]nie obsługują const map, przynajmniej nie, implementacji biblioteki C ++ g ++.
Craig McQueen
2
Błąd: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
Rzeczywiście, operator mapy [] nie może działać na mapie const, ponieważ ten operator tworzy wpis, do którego się odwołuje, jeśli nie istnieje (ponieważ zwraca odniesienie do odwzorowanej wartości). C ++ 11 wprowadził metodę at (KeyValT key), która umożliwia dostęp do elementu z podanym kluczem, zgłaszając wyjątek, jeśli nie istnieje. ( en.cppreference.com/w/cpp/container/map/at ) Ta metoda działa na instancjach stałych, ale nie można jej użyć do wstawienia elementu do instancji innej niż stała (tak jak operator []).
mbargiel
108

W standardzie C ++ 11 wprowadzono jednolitą inicjalizację, co znacznie upraszcza, jeśli Twój kompilator to obsługuje:

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


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

Zobacz także tę sekcję z Professional C ++ , na unordered_maps.

David C. Bishop
źródło
Czy w ogóle potrzebujemy znaku równości w pliku cpp?
phoad
@phoad: Znak równości jest zbędny.
Jinxed
Dziękuję za pokazanie użycia. Bardzo pomocne było zrozumienie, jak modyfikować zmienne statyczne.
User9102d82
12

Ja to zrobiłem! :)

Działa dobrze bez 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
źródło
11

Jeśli uznasz to za boost::assign::map_list_ofprzydatne, ale z jakiegoś powodu nie możesz go użyć, możesz napisać własne :

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';
}

Warto wiedzieć, jak takie rzeczy działają, zwłaszcza gdy są takie krótkie, ale w tym przypadku użyłbym funkcji:

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
źródło
6

Inne podejście do problemu:

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
}

Jest to bardziej wydajne, ponieważ nie ma jednego typu kopiowania ze stosu na stertę (w tym konstruktor, destruktory na wszystkich elementach). To, czy ma to znaczenie, czy nie, zależy od twojego przypadku użycia. Nie ma znaczenia w przypadku sznurków! (ale możesz znaleźć tę wersję „czystszą” lub nie)

ypnos
źródło
3
RVO eliminuje kopiowanie w odpowiedzi mojej i Neila.
6

Jeśli mapa ma zawierać tylko wpisy, które są znane w czasie kompilacji, a klucze do mapy są liczbami całkowitymi, to w ogóle nie musisz używać mapy.

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
źródło
5
+1 za wskazanie, że mapa nie jest potrzebna, jednak nie możesz tego iterować
Viktor Sehr
4
To switchjest jednak okropne. Dlaczego nie return key + 'a' - 1?
Johnsyweb
12
@Johnsyweb. Zakładam, że mapowanie dostarczone przez oryginalnego plakatu zostało przedstawione wyłącznie jako przykład i nie wskazuje na rzeczywiste odwzorowanie, które posiada. Dlatego też zakładam, że return key + 'a' - 1to nie zadziała w przypadku jego rzeczywistego mapowania.
Matthew T. Staebler
3

Możesz spróbować tego:

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

W tej implementacji stała statyczna mapa klas jest członkiem prywatnym i może być dostępna dla innych klas przy użyciu publicznej metody get. W przeciwnym razie, ponieważ jest stała i nie można jej zmienić, możesz usunąć publiczną metodę get i przenieść zmienną mapy do sekcji public class. Pozostawiłbym jednak metodę createMap jako prywatną lub chronioną, jeśli wymagane jest dziedziczenie i / lub polimorfizm. Oto kilka przykładów użycia.

 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.

Zredagowałem mój oryginalny post, nie było nic złego w oryginalnym kodzie, w którym napisałem, skompilowałem, zbudowałem i uruchomiłem poprawnie, po prostu moja pierwsza wersja przedstawiłem jako odpowiedź, że mapa została uznana za publiczną, a mapa została const, ale nie było statyczne.

Francis Cugler
źródło
2

Jeśli używasz kompilatora, który nadal nie obsługuje uniwersalnej inicjalizacji lub masz zastrzeżenia co do korzystania z funkcji Boost, inna możliwa alternatywa byłaby następująca

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
źródło
0

Wywołanie funkcji nie może pojawić się w wyrażeniu stałym.

spróbuj tego: (tylko przykład)

#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
źródło
6
Z pewnością można użyć funkcji do zainicjowania obiektu const.
W OP kod static map<int,int> myMap = create_map();jest nieprawidłowy.
Prasoon Saurav
3
Kod w pytaniu jest błędny, wszyscy się na to zgadzamy, ale nie ma to nic wspólnego z „stałymi wyrażeniami”, jak to powiesz w tej odpowiedzi, ale raczej z faktem, że możesz zainicjować tylko stałe statyczne elementy klasy w deklaracja, jeśli są typu całkowitego lub wyliczeniowego. W przypadku wszystkich innych typów inicjalizację należy wykonać w definicji elementu członkowskiego, a nie w deklaracji.
David Rodríguez - dribeas
Odpowiedź Neila składa się z g ++. Mimo to pamiętam, że miałem pewne problemy z tym podejściem we wcześniejszych wersjach łańcucha narzędzi GNU. Czy istnieje uniwersalna prawidłowa odpowiedź?
Basilevs,
1
@Prasoon: Nie wiem, co mówi kompilator, ale błąd w kodzie pytania polega na inicjalizacji stałego atrybutu typu klasy w deklaracji klasy, niezależnie od tego, czy inicjalizacja jest wyrażeniem stałym, czy nie. Jeśli zdefiniujesz klasę: struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td;kompilacja nie powiedzie się, nawet jeśli inicjalizacja jest wykonywana za pomocą stałego wyrażenia ( 5). Oznacza to, że „wyrażenie stałe” nie ma znaczenia dla poprawności (lub jej braku) początkowego kodu.
David Rodríguez - dribeas
-2

Często używam tego wzoru i polecam go również:

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;

Jasne, że nie jest zbyt czytelny, ale bez innych bibliotek jest to najlepsze, co możemy zrobić. Nie będzie też żadnych zbędnych operacji, takich jak kopiowanie z jednej mapy na drugą, jak w przypadku twojej próby.

Jest to jeszcze bardziej przydatne w funkcjach: Zamiast:

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

Użyj następujących:

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

Nie tylko nie musisz już tutaj zajmować się zmienną boolowską, nie będziesz mieć ukrytej zmiennej globalnej, która jest sprawdzana, jeśli inicjator zmiennej statycznej wewnątrz funkcji został już wywołany.

Pavel Chikulaev
źródło
6
Dziedziczenie powinno być narzędziem ostatecznym, a nie pierwszym.
Kompilator obsługujący RVO eliminuje zbędne kopiowanie z wersjami funkcji. Semantyka ruchu C ++ 0x eliminuje resztę, gdy tylko będą dostępne. W każdym razie wątpię, czy jest to wąskie gardło.
Roger, dobrze znam RVO, && i semantykę ruchu. Na razie jest to rozwiązanie przy minimalnej ilości kodu i encji. Ponadto wszystkie funkcje C ++ 0x nie pomogą w przypadku statycznego obiektu wewnątrz przykładu funkcji, ponieważ nie możemy definiować funkcji wewnątrz funkcji.
Pavel Chikulaev