Wie initialisiere ich eine private statische const map in C ++?

108

Ich brauche nur ein Wörterbuch oder ein assoziatives Array string=> int.

Für diesen Fall gibt es die Typzuordnung C ++.

Aber ich brauche nur eine Map für alle Instanzen (-> statisch) und diese Map kann nicht geändert werden (-> const);

Ich habe diesen Weg mit der Boost-Bibliothek gefunden

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

Gibt es eine andere Lösung ohne diese Bibliothek? Ich habe so etwas versucht, aber es gibt immer einige Probleme mit der Karteninitialisierung.

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
quelle
1
Auf welche Themen beziehen Sie sich? Versuchen Sie, diese Zuordnung von einer anderen globalen statischen Variablen / Konstante zu verwenden?
Péter Török
Das ist keine assoziative Array-Zeichenfolge => int, Sie ordnen ein int einem Zeichen zu. v = k + 'a' - 1.
Johnsyweb

Antworten:

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

quelle
3
+1 der Einfachheit halber ist es natürlich auch Boost.Assignziemlich ordentlich , ein ähnliches Design zu verwenden :)
Matthieu M.
5
+1, danke. Hinweis: Ich musste die Initialisierungszeile in meine Implementierungsdatei einfügen. Das Belassen in der Header-Datei führte zu Fehlern aufgrund mehrerer Definitionen (der Initialisierungscode wurde immer dann ausgeführt, wenn der Header irgendwo enthalten war).
System.Cats.Lol
1
Mit g ++ v4.7.3, Dies kompiliert, bis ich hinzufügen cout << A::myMap[1];in main(). Es gibt einen Fehler. Der Fehler tritt nicht auf, wenn ich die constQualifikationsmerkmale entferne. Ich denke, dass Map's zumindest in der g ++ - Implementierung der C ++ - Bibliothek operator[]nicht mit a umgehen können const map.
Craig McQueen
2
Fehler ist: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
In der Tat kann der Operator [] der Karte keine konstante Karte bearbeiten, da dieser Operator den referenzierten Eintrag erstellt, wenn er nicht vorhanden ist (da er einen Verweis auf den zugeordneten Wert zurückgibt). In C ++ 11 wurde die at-Methode (KeyValT-Schlüssel) eingeführt, mit der Sie mit einem bestimmten Schlüssel auf das Element zugreifen und eine Ausnahme auslösen können, wenn es nicht vorhanden ist. ( en.cppreference.com/w/cpp/container/map/at ) Diese Methode funktioniert für const-Instanzen, kann jedoch nicht zum Einfügen eines Elements in eine nicht-const-Instanz verwendet werden (wie auch der Operator []).
mbargiel
108

Der C ++ 11-Standard führte eine einheitliche Initialisierung ein, die dies viel einfacher macht, wenn Ihr Compiler dies unterstützt:

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


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

Siehe auch diesen Abschnitt aus Professional C ++ unter unordered_maps.

David C. Bishop
quelle
Benötigen wir überhaupt das Gleichheitszeichen in der CPP-Datei?
Phoad
@phoad: Das Gleichheitszeichen ist überflüssig.
Jinxed
Vielen Dank, dass Sie die Verwendung gezeigt haben. Es war sehr hilfreich zu verstehen, wie die statischen Variablen geändert werden.
User9102d82
12

Ich hab es gemacht! :) :)

Funktioniert gut ohne 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
quelle
11

Wenn Sie es boost::assign::map_list_ofnützlich finden , es aber aus irgendeinem Grund nicht verwenden können, können Sie Ihr eigenes schreiben :

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

Es ist nützlich zu wissen, wie solche Dinge funktionieren, besonders wenn sie so kurz sind, aber in diesem Fall würde ich eine Funktion verwenden:

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
quelle
6

Eine andere Herangehensweise an das Problem:

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
}

Dies ist effizienter, da es keine Kopie eines Typs vom Stapel zum Heap gibt (einschließlich Konstruktor, Destruktoren für alle Elemente). Ob dies wichtig ist oder nicht, hängt von Ihrem Anwendungsfall ab. Bei Saiten spielt das keine Rolle! (aber Sie können diese Version "sauberer" finden oder auch nicht)

ypnos
quelle
3
RVO eliminiert das Kopieren in meiner und Neils Antwort.
6

Wenn die Karte nur Einträge enthalten soll, die zur Kompilierungszeit bekannt sind, und die Schlüssel für die Karte Ganzzahlen sind, müssen Sie überhaupt keine Karte verwenden.

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
quelle
5
+1 für den Hinweis, dass eine Karte nicht benötigt wird, Sie können dies jedoch nicht wiederholen
Viktor Sehr
4
Das switchist allerdings schrecklich. Warum nicht return key + 'a' - 1?
Johnsyweb
12
@ Johnsyweb. Ich gehe davon aus, dass das vom Originalplakat gelieferte Mapping nur als Beispiel präsentiert wurde und nicht auf das tatsächliche Mapping hinweist, das er hat. Daher würde ich auch davon ausgehen, dass return key + 'a' - 1dies für sein eigentliches Mapping nicht funktionieren würde.
Matthew T. Staebler
3

Sie könnten dies versuchen:

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

Mit dieser Implementierung ist die statische Map Ihrer Klassenkonstante ein privates Mitglied und kann mithilfe einer öffentlichen get-Methode für andere Klassen zugänglich sein. Andernfalls können Sie die public get-Methode entfernen und die Map-Variable in den öffentlichen Abschnitt der Klasse verschieben, da sie konstant ist und sich nicht ändern kann. Ich würde jedoch die createMap-Methode privat oder geschützt lassen, wenn Vererbung und / oder Polymorphismus erforderlich sind. Hier sind einige Anwendungsbeispiele.

 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.

Ich hatte meinen ursprünglichen Beitrag bearbeitet, es war nichts Falsches an dem ursprünglichen Code, in dem ich ihn gepostet habe, kompiliert, erstellt und korrekt ausgeführt. Es war nur so, dass meine erste Version, die ich als Antwort präsentierte, die Karte als öffentlich deklariert wurde und die Karte war const war aber nicht statisch.

Francis Cugler
quelle
2

Wenn Sie einen Compiler verwenden, der die universelle Initialisierung immer noch nicht unterstützt, oder wenn Sie Bedenken haben, Boost zu verwenden, ist eine andere mögliche Alternative die folgende

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
quelle
0

Ein Funktionsaufruf kann nicht in einem konstanten Ausdruck erscheinen.

versuchen Sie dies: (nur ein Beispiel)

#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
quelle
6
Eine Funktion kann sicherlich verwendet werden, um ein const-Objekt zu initialisieren.
Im OP ist der Code static map<int,int> myMap = create_map();falsch.
Prasoon Saurav
3
Der Code in der Frage ist falsch, wir sind uns alle einig, aber er hat nichts mit 'konstanten Ausdrücken' zu tun, wie Sie in dieser Antwort sagen, sondern mit der Tatsache, dass Sie nur konstante statische Mitglieder einer Klasse in der Frage initialisieren können Deklaration, wenn sie vom Typ Integer oder Enum sind. Bei allen anderen Typen muss die Initialisierung in der Elementdefinition und nicht in der Deklaration erfolgen.
David Rodríguez - Dribeas
Neils Antwort wird mit g ++ kompiliert. Trotzdem erinnere ich mich, dass ich in früheren Versionen der GNU-Toolchain einige Probleme mit diesem Ansatz hatte. Gibt es eine universelle richtige Antwort?
Basilevs
1
@Prasoon: Ich weiß nicht, was der Compiler sagt, aber der Fehler im Fragencode initialisiert ein konstantes Mitgliedsattribut vom Klassentyp in der Klassendeklaration, unabhängig davon, ob die Initialisierung ein konstanter Ausdruck ist oder nicht. Wenn Sie eine Klasse definieren: struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td;Sie kann nicht kompiliert werden, selbst wenn die Initialisierung mit einem konstanten Ausdruck ( 5) durchgeführt wird. Das heißt, "konstanter Ausdruck" ist für die Richtigkeit (oder das Fehlen) des ursprünglichen Codes irrelevant.
David Rodríguez - Dribeas
-2

Ich benutze dieses Muster oft und empfehle Ihnen, es auch zu verwenden:

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;

Sicher ist es nicht sehr lesbar, aber ohne andere Bibliotheken ist es das Beste, was wir tun können. Es wird auch keine redundanten Vorgänge wie das Kopieren von einer Karte auf eine andere geben, wie bei Ihrem Versuch.

Dies ist innerhalb von Funktionen noch nützlicher: Anstelle von:

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

Verwenden Sie Folgendes:

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

Sie müssen sich hier nicht nur nicht mehr mit booleschen Variablen befassen, sondern haben auch keine versteckte globale Variable, die überprüft wird, ob der Initialisierer der statischen Variablen innerhalb der Funktion bereits aufgerufen wurde.

Pavel Chikulaev
quelle
6
Vererbung sollte das letzte Mittel sein, nicht das erste.
Ein Compiler, der RVO unterstützt, eliminiert redundantes Kopieren mit den Funktionsversionen. Die C ++ 0x-Verschiebungssemantik eliminiert den Rest, sobald sie verfügbar sind. Auf jeden Fall bezweifle ich, dass es fast ein Engpass ist.
Roger, ich bin mir der RVO und der Bewegungssemantik sehr wohl bewusst. Dies ist vorerst eine Lösung mit minimaler Menge an Code und Entitäten. Außerdem helfen alle C ++ 0x-Funktionen nicht bei statischen Objekten innerhalb eines Funktionsbeispiels, da wir keine Funktionen innerhalb von Funktionen definieren dürfen.
Pavel Chikulaev