Initialisieren einer statischen std :: map <int, int> in C ++

447

Was ist der richtige Weg, um eine statische Karte zu initialisieren? Benötigen wir eine statische Funktion, die sie initialisiert?

Nithin
quelle

Antworten:

619

Verwenden von C ++ 11:

#include <map>
using namespace std;

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

Verwenden von 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
quelle
115
Jedes Mal, wenn ich so etwas mit C ++ sehe, denke ich an all den schrecklichen Vorlagencode, der dahinter stecken muss. Gutes Beispiel!
Greg Hewgill
34
Das Schöne an all dem schrecklichen Vorlagencode, der diese Dienstprogramme implementiert, ist, dass er ordentlich in einer Bibliothek gekapselt ist und der Endbenutzer sich selten mit der Komplexität auseinandersetzen muss.
Steve Guidi
45
@QBziZ: Wenn Ihr Unternehmen Erhöhung lehnt mit der Begründung , es nicht „Standard genug“ zu sein, frage ich mich , was C ++ Bibliothek würde „Standard genug“ sein. Boost ist der Standardbegleiter für den C ++ - Codierer.
DevSolar
47
Mein Problem mit Boost (hier und anderswo) ist, dass Sie oft ohne Boost auskommen können (in diesem Fall mit C ++ 11 oder vor C ++ 11 mit einer Funktion ). Boost fügt einen erheblichen Aufwand für die Kompilierung hinzu, hatte Tonnen von Dateien, die in Ihrem Repository geparkt werden mussten (und um / zip / extract zu kopieren, wenn Sie ein Archiv erstellen). Das ist der Grund, warum ich versuche, es nicht zu benutzen. Ich weiß, dass Sie auswählen können, welche Dateien eingeschlossen oder nicht eingeschlossen werden sollen, aber Sie möchten sich normalerweise nicht um die gegenseitigen Abhängigkeiten von Boost kümmern müssen, sodass Sie einfach das Ganze kopieren.
Bobobobo
7
Mein Problem mit Boost ist, dass es häufig mehrere neue Bibliotheksabhängigkeiten gibt, was im Allgemeinen bedeutet, dass MEHR Pakete installiert werden müssen, um ordnungsgemäß zu funktionieren. Wir brauchen bereits libstdc ++. Für die Boost ASIO-Bibliothek sind beispielsweise mindestens zwei neue Bibliotheken (wahrscheinlich mehr) erforderlich, die installiert werden müssen. C ++ 11/14 macht es viel einfacher, Boost nicht zu benötigen.
Rahly
135

Der beste Weg ist, eine Funktion zu verwenden:

#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
quelle
18
Warum ist das das Beste? Warum ist es zum Beispiel besser als die Antwort von @ Dreamer?
Marquis von Lorne
6
Ich denke, es ist "am besten", weil es wirklich einfach ist und nicht von anderen vorhandenen Strukturen abhängt (wie dem Boost :: Assign oder einer Neuimplementierung davon). Und im Vergleich zu @ Dreamers Antwort vermeide ich es, eine ganze Struktur nur zum Initialisieren einer Karte zu
erstellen
3
Beachten Sie, dass hier eine Gefahr besteht . externVariablen haben in diesem "Konstruktor vor der Hauptlaufzeit" nicht die richtigen Werte, wenn der Compiler nur die externDeklaration gesehen hat, aber noch nicht auf die eigentliche Variablendefinition gestoßen ist .
Bobobobo
5
Nein, die Gefahr besteht darin, dass nichts darüber aussagt, in welcher Reihenfolge die statischen Variablen initialisiert werden sollen (zumindest über Kompilierungseinheiten hinweg). Dies ist jedoch kein Problem im Zusammenhang mit dieser Frage. Dies ist ein allgemeines Problem bei statischen Variablen.
PierreBdR
5
kein Boost UND kein C ++ 11 => +1. Beachten Sie, dass die Funktion verwendet werden kann, um a zu initialisieren const map<int,int> m = create_map()(und so const-Mitglieder einer Klasse in der Initialisierungsliste zu initialisieren:struct MyClass {const map<int, int> m; MyClass(); }; MyClass::MyClass() : m(create_map())
ribamar
115

Es ist kein kompliziertes Problem, etwas Ähnliches zu machen, um es zu verbessern. Hier ist eine Klasse mit nur drei Funktionen, einschließlich des Konstruktors, um zu replizieren, was Boost (fast) bewirkt hat.

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

Verwendungszweck:

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

Der obige Code eignet sich am besten für die Initialisierung globaler Variablen oder statischer Elemente einer Klasse, die initialisiert werden muss, und Sie haben keine Ahnung, wann er zuerst verwendet wird, möchten jedoch sicherstellen, dass die Werte darin verfügbar sind.

Wenn Sie sagen, Sie müssen Elemente in eine vorhandene std :: map einfügen ... hier ist eine andere Klasse für Sie.

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

Verwendungszweck:

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

Sehen Sie es in Aktion mit GCC 4.7.2 hier: http://ideone.com/3uYJiH

############### ALLES UNTEN IST OBSOLET ##################

BEARBEITEN : Die folgende map_add_valuesKlasse, die die ursprüngliche Lösung war, die ich vorgeschlagen hatte, würde in Bezug auf GCC 4.5+ fehlschlagen. Im obigen Code erfahren Sie, wie Sie der vorhandenen Karte Werte hinzufügen .


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

Verwendungszweck:

std :: map <int, int> my_map;
// Später irgendwo entlang des Codes
map_add_values ​​<int, int> (my_map) (1,2) (3,4) (5,6);

HINWEIS: Zuvor habe ich a operator []zum Hinzufügen der tatsächlichen Werte verwendet. Dies ist nicht möglich, wie von dalle kommentiert.

##################### ENDE DES OBSOLETEN ABSCHNITTS #####################

Vite Falcon
quelle
3
Ich verwende Ihr erstes Beispiel als <int, string>, um Fehlernummern (aus einer Aufzählung) mit Nachrichten zu verbinden - es funktioniert wie ein Zauber - danke.
Slashmais
1
operator[]nimmt nur ein einziges Argument.
Dale
1
@dalle: Guter Fang! Aus irgendeinem Grund dachte ich, überladene [] Operatoren könnten mehr akzeptieren.
Vite Falcon
2
Dies ist eine fantastische Antwort. Es ist eine Schande, dass das OP nie einen ausgewählt hat. Sie verdienen Mega-Requisiten.
Thomas Thorogood
Die map_add_values ​​funktionieren nicht in gcc, was sich beschwert: 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

Hier ist eine andere Möglichkeit, den 2-Element-Datenkonstruktor zu verwenden. Zum Initialisieren sind keine Funktionen erforderlich. Es gibt keinen Code von Drittanbietern (Boost), keine statischen Funktionen oder Objekte, keine Tricks, nur einfaches C ++:

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

Seit ich diese Antwort geschrieben habe, ist C ++ 11 raus. Sie können STL-Container jetzt direkt mit der neuen Funktion zur Initialisierungsliste initialisieren:

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

Zum Beispiel:

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

Wenn map ein Datenelement einer Klasse ist, können Sie es wie folgt direkt im Header initialisieren (seit 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
quelle
24

Ich würde die Karte in ein statisches Objekt einwickeln und den Karteninitialisierungscode in den Konstruktor dieses Objekts einfügen. Auf diese Weise können Sie sicher sein, dass die Karte erstellt wird, bevor der Initialisierungscode ausgeführt wird.

Drealmer
quelle
1
Ich bin mit dir in diesem Fall. Es ist auch ein bisschen schneller :)
QBziZ
2
Tad schneller als was? Eine globale Statik mit einem Initialisierer? Nein, ist es nicht (denken Sie an RVO).
Pavel Minaev
7
Gute Antwort. Ich würde mich freuen, wenn ich den tatsächlichen Beispielcode sehen würde
Sungguk Lim
18

Ich wollte nur eine reine C ++ 98-Lösung teilen:

#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
quelle
2
Dies funktioniert nicht für Objekte ohne Standardkonstruktor. Einfügemethode sollte IMHO
Alessandro Teruzzi
16

Du kannst es versuchen:

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
quelle
1
Sie können vor C ++ 11 keine Initialisierungslisten mit nicht aggregierten Typen verwenden. In diesem Fall können Sie auch die kürzere Syntax {1, 2}anstelle von verwenden std::pair<int, int>(1, 2).
Ferruccio
9

Dies ist ähnlich wie PierreBdRohne die Karte zu kopieren.

#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);
nervös
quelle
12
Es wäre wahrscheinlich sowieso nicht kopiert worden.
GManNickG
2
Aber auf diese Weise konnte die Karte keine statische Konstante sein, oder?
Xmoex
6

Wenn Sie mit C ++ 98 nicht weiterkommen und Boost nicht verwenden möchten, gibt es hier die Lösung, die ich verwende, wenn ich eine statische Map initialisieren muss:

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

Sie haben hier einige sehr gute Antworten, aber ich bin für mich, es sieht aus wie ein Fall von "wenn alles, was Sie wissen, ein Hammer ist" ...

Die einfachste Antwort darauf, warum es keine Standardmethode zum Initialisieren einer statischen Karte gibt, ist, dass es keinen guten Grund gibt, jemals eine statische Karte zu verwenden ...

Eine Karte ist eine Struktur, die für die schnelle Suche nach einem unbekannten Satz von Elementen entwickelt wurde. Wenn Sie die Elemente vorher kennen, verwenden Sie einfach ein C-Array. Geben Sie die Werte sortiert ein oder sortieren Sie sie, wenn Sie dies nicht können. Sie können dann die Leistung von log (n) erhalten, indem Sie die stl :: -Funktionen verwenden, um Einträge (lower_bound / obere_bound) zu schleifen. Wenn ich dies zuvor getestet habe, arbeiten sie normalerweise mindestens viermal schneller als eine Karte.

Die Vorteile sind vielfältig ... - schnellere Leistung (* 4, ich habe an vielen CPU-Typen gemessen, es sind immer etwa 4) - einfacheres Debuggen. Mit einem linearen Layout ist es einfacher zu sehen, was los ist. - Triviale Implementierungen von Kopiervorgängen, falls dies erforderlich werden sollte. - Es weist zur Laufzeit keinen Speicher zu und löst daher niemals eine Ausnahme aus. - Es ist eine Standardschnittstelle und daher sehr einfach für DLLs, Sprachen usw. freizugeben.

Ich könnte weitermachen, aber wenn Sie mehr wollen, schauen Sie sich doch die vielen Blogs von Stroustrup zu diesem Thema an.

user2185945
quelle
8
Die Leistung ist nicht der einzige Grund für die Verwendung einer Karte. Beispielsweise gibt es viele Fälle, in denen Sie Werte miteinander verknüpfen möchten (z. B. einen Fehlercode mit einer Fehlermeldung), und eine Karte vereinfacht die Verwendung und den Zugriff relativ einfach. Aber ein Link zu diesen Blogeinträgen kann interessant sein, vielleicht mache ich etwas falsch.
MatthiasB
5
Ein Array ist viel einfacher und hat eine höhere Leistung, wenn Sie es verwenden können. Wenn die Indizes (Schlüssel) jedoch nicht zusammenhängend und weit auseinander liegen, benötigen Sie eine Karte.
KarlU
1
A mapist auch eine nützliche Form zur Darstellung einer Teilfunktion (Funktion im mathematischen Sinne; aber auch im Programmiersinn). Ein Array macht das nicht. Sie können beispielsweise keine Daten aus einem Array mithilfe einer Zeichenfolge suchen.
Einpoklum
3
Ihre Antwort versucht nicht, die gültige Frage zu beantworten, sondern spekuliert über die Einschränkungen der Sprache, schlägt Lösungen für verschiedene Probleme vor und stimmt daher ab. Ein reales Szenario - Zuordnung von (fortlaufenden oder nicht fortlaufenden) Bibliotheksfehlercodes zu Textzeichenfolgen. Bei einem Array beträgt die Suchzeit O (n), was durch statische Zuordnung zu O (log (n)) verbessert werden kann.
Tosha
2
Wenn in der Tat "es gibt keinen guten Grund, jemals eine statische Zuordnung zu verwenden ...", ist es sehr seltsam, dass in C ++ 11 eine Syntax (Initialisierungslisten) hinzugefügt wurde, die die Verwendung vereinfacht.
Ellisbben