Initialisation d'un std :: map <int, int> statique en C ++

448

Quelle est la bonne façon d'initialiser une carte statique? Avons-nous besoin d'une fonction statique qui l'initialise?

Nithin
la source

Réponses:

619

En utilisant C ++ 11:

#include <map>
using namespace std;

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

Utilisation de 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
la source
115
Chaque fois que je vois quelque chose comme ça fait avec C ++, je pense à tout le code du modèle horrible qui doit être derrière. Bon exemple!
Greg Hewgill, le
34
La beauté de tout le code de modèle horrible qui implémente ces utilitaires est qu'il est soigneusement encapsulé dans une bibliothèque et que l'utilisateur final a rarement besoin de gérer la complexité.
Steve Guidi
45
@QBziZ: Si votre entreprise refuse d'utiliser Boost au motif qu'elle n'est pas "assez standard", je me demande quelle bibliothèque C ++ serait "assez standard". Boost est le compagnon standard du codeur C ++.
DevSolar
47
Mon problème avec Boost (ici et ailleurs) est que vous pouvez souvent vous en passer (dans ce cas avec C ++ 11 ou avant C ++ 11 avec une fonction ). Boost ajoute une surcharge de temps de compilation importante, avait des tonnes de fichiers à garer dans votre référentiel (et à copier / zip / extraire si vous faites une archive). C'est la raison pour laquelle j'essaie de ne pas l'utiliser. Je sais que vous pouvez choisir les fichiers à inclure / ne pas inclure, mais vous ne voulez généralement pas vous soucier des dépendances croisées de Boost avec lui-même, vous devez donc simplement copier le tout.
bobobobo
7
Mon problème avec Boost est qu'il a souvent plusieurs nouvelles dépendances de bibliothèque, ce qui signifie généralement PLUS de packages qui doivent être installés pour fonctionner correctement. Nous avons déjà besoin de libstdc ++. Par exemple, la bibliothèque Boost ASIO nécessite au moins 2 nouvelles bibliothèques (probablement plus) qui doivent être installées. C ++ 11/14 rend beaucoup plus facile de ne pas avoir besoin de Boost.
Rahly
135

Le meilleur moyen est d'utiliser une fonction:

#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
la source
18
Pourquoi est-ce le «meilleur»? Pourquoi par exemple est-ce mieux que la réponse de @ Dreamer?
Marquis de Lorne
6
Je pense que c'est "le meilleur" car il est vraiment simple et ne dépend pas d'autres structures existantes (comme le Boost :: Assign ou sa réimplémentation). Et par rapport à la réponse de @ Dreamer, eh bien, j'évite de créer une structure entière uniquement pour initialiser une carte ...
PierreBdR
3
Notez qu'il y a un danger ici . externles variables n'auront pas leurs valeurs correctes dans ce «avant le constructeur principal» si le compilateur n'a vu que la externdéclaration, mais n'a pas encore exécuté la définition de variable réelle .
bobobobo
5
Non, le danger est que rien ne dit dans quel ordre les variables statiques doivent être initialisées (au moins sur les unités de compilation). Mais ce n'est pas un problème lié à cette question. Il s'agit d'un problème général avec les variables statiques.
PierreBdR
5
pas de boost ET pas de C ++ 11 => +1. Notez que la fonction peut être utilisée pour initialiser un const map<int,int> m = create_map()(et ainsi, initialiser les membres const d'une classe dans la liste d'initialisation:struct MyClass {const map<int, int> m; MyClass(); }; MyClass::MyClass() : m(create_map())
ribamar
115

Ce n'est pas un problème compliqué de créer quelque chose de similaire à stimuler. Voici une classe avec seulement trois fonctions, y compris le constructeur, pour reproduire ce que boost a fait (presque).

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

Usage:

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

Le code ci-dessus fonctionne mieux pour l'initialisation de variables globales ou de membres statiques d'une classe qui doit être initialisée et vous ne savez pas quand il sera utilisé en premier, mais vous voulez vous assurer que les valeurs y sont disponibles.

Si disons, vous devez insérer des éléments dans une carte std :: map existante ... voici une autre classe pour vous.

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

Usage:

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

Voyez-le en action avec GCC 4.7.2 ici: http://ideone.com/3uYJiH

############### TOUT CI-DESSOUS C'EST OBSOLÈTE #################

EDIT : La map_add_valuesclasse ci-dessous, qui était la solution originale que j'avais suggérée, échouerait en ce qui concerne GCC 4.5+. Veuillez consulter le code ci-dessus pour savoir comment ajouter des valeurs à la carte existante.


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

Usage:

std :: map <int, int> my_map;
// Plus tard quelque part le long du code
map_add_values ​​<int, int> (my_map) (1,2) (3,4) (5,6);

REMARQUE: Auparavant, j'utilisais un operator []pour ajouter les valeurs réelles. Ce n'est pas possible comme l'a commenté dalle.

##################### FIN DE LA SECTION OBSOLETE #####################

Vite Falcon
la source
3
J'utilise votre premier échantillon en tant que <int, string> pour lier les numéros d'erreur (à partir d'une énumération) aux messages - cela fonctionne comme un charme - merci.
slashmais
1
operator[]ne prend qu'un seul argument.
dalle
1
@dalle: Bonne prise! Pour une raison quelconque, je pensais que les opérateurs surchargés [] pouvaient en accepter davantage.
Vite Falcon
2
Ceci est une réponse fantastique. C'est dommage que l'OP n'en ait jamais sélectionné. Vous méritez des méga-accessoires.
Thomas Thorogood
les map_add_values ​​ne fonctionnent pas dans gcc, ce qui se plaint: 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

Voici une autre façon d'utiliser le constructeur de données à 2 éléments. Aucune fonction n'est nécessaire pour l'initialiser. Il n'y a pas de code tiers (Boost), pas de fonctions ou d'objets statiques, pas de trucs, juste du C ++ simple:

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

Depuis que j'ai écrit cette réponse, C ++ 11 est sorti. Vous pouvez maintenant initialiser directement les conteneurs STL à l'aide de la nouvelle fonctionnalité de liste d'initialisation:

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

Par exemple:

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

Si map est un membre de données d'une classe, vous pouvez l'initialiser directement dans l'en-tête de la manière suivante (depuis 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
la source
24

J'envelopperais la carte dans un objet statique et mettrais le code d'initialisation de la carte dans le constructeur de cet objet, de cette façon, vous êtes sûr que la carte est créée avant l'exécution du code d'initialisation.

Drealmer
la source
1
Je suis avec toi sur celui-ci. C'est aussi un peu plus rapide :)
QBziZ
2
Un peu plus vite que quoi? Une statique globale avec un initialiseur? Non, ce n'est pas le cas (souvenez-vous de RVO).
Pavel Minaev,
7
Bonne réponse. Je serais heureux si je vois le code d'exemple réel
Sungguk Lim
18

Je voulais juste partager un pur travail C ++ 98:

#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
la source
2
cela ne fonctionne pas pour l'objet sans constructeur par défaut, la méthode d'insertion devrait être préférée IMHO
Alessandro Teruzzi
16

Tu peux essayer:

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
la source
1
Vous ne pouvez pas utiliser de listes d'initialisation avec des types non agrégés avant C ++ 11, auquel cas vous pouvez également utiliser la syntaxe plus courte à la {1, 2}place de std::pair<int, int>(1, 2).
Ferruccio
9

Ceci est similaire à PierreBdR, sans copier la carte.

#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
la source
12
Il n'aurait probablement pas été copié de toute façon.
GManNickG
2
mais de cette façon la carte ne pouvait pas être une constante statique, n'est-ce pas?
xmoex
6

Si vous êtes coincé avec C ++ 98 et que vous ne souhaitez pas utiliser boost, voici la solution que j'utilise lorsque j'ai besoin d'initialiser une carte statique:

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
la source
-4

Vous avez de très bonnes réponses ici, mais je suis pour moi, cela ressemble à un cas de "quand tout ce que vous savez c'est un marteau" ...

La réponse la plus simple à la raison pour laquelle il n'y a pas de moyen standard d'initialiser une carte statique, c'est qu'il n'y a aucune bonne raison d'utiliser une carte statique ...

Une carte est une structure conçue pour une recherche rapide, d'un ensemble inconnu d'éléments. Si vous connaissez les éléments à l'avance, utilisez simplement un tableau C. Entrez les valeurs de manière triée ou exécutez le tri sur celles-ci si vous ne pouvez pas le faire. Vous pouvez ensuite obtenir les performances de log (n) en utilisant les fonctions stl :: pour boucler les entrées, lower_bound / upper_bound. Lorsque j'ai testé cela auparavant, ils fonctionnent normalement au moins 4 fois plus vite qu'une carte.

Les avantages sont multiples ... - des performances plus rapides (* 4, j'ai mesuré sur de nombreux types de CPU, c'est toujours autour de 4) - un débogage plus simple. Il est simplement plus facile de voir ce qui se passe avec une disposition linéaire. - Implémentations triviales d'opérations de copie, si cela devenait nécessaire. - Il n'alloue aucune mémoire au moment de l'exécution, donc ne lèvera jamais d'exception. - Il s'agit d'une interface standard et est donc très facile à partager entre les DLL, les langues, etc.

Je pourrais continuer, mais si vous en voulez plus, pourquoi ne pas consulter les nombreux blogs de Stroustrup sur le sujet.

user2185945
la source
8
Les performances ne sont pas la seule raison d'utiliser une carte. Par exemple, il existe de nombreux cas où vous souhaitez lier des valeurs (par exemple, un code d'erreur avec un message d'erreur), et une carte rend l'utilisation et l'accès relativement simples. Mais un lien vers ces entrées de blog peut être intéressant, peut-être que je fais quelque chose de mal.
MatthiasB
5
Un tableau est beaucoup plus facile et a de meilleures performances si vous pouvez l'utiliser. Mais si les indices (clés) ne sont pas contigus et largement espacés, vous avez besoin d'une carte.
KarlU
1
A mapest également une forme utile pour représenter une fonction partielle (fonction au sens mathématique; mais aussi, en quelque sorte, au sens de la programmation). Un tableau ne fait pas cela. Vous ne pouvez pas, par exemple, rechercher des données à partir d'un tableau à l'aide d'une chaîne.
einpoklum
3
Votre réponse ne tente pas de répondre à la question valable, et spécule plutôt sur les limites du langage, propose des solutions à différents problèmes, d'où un vote négatif. Un vrai scénario - mappage (continu ou non) des codes d'erreur de bibliothèque aux chaînes de texte. Avec array, le temps de recherche est O (n), ce qui peut être amélioré par un mappage statique vers O (log (n)).
Tosha
2
Si en effet "il n'y a aucune bonne raison d'utiliser jamais une carte statique ..." alors il est très étrange que la syntaxe (listes d'initialisation) qui les rendent faciles à utiliser ait été ajoutée en C ++ 11.
ellisbben