Comment initialiser une carte de const statique privée en C ++?

108

J'ai juste besoin d'un dictionnaire ou d'un tableau associatif string=> int.

Il existe une carte de type C ++ pour ce cas.

Mais je n'ai besoin que d'une seule carte pour toutes les instances (-> statique) et cette carte ne peut pas être modifiée (-> const);

J'ai trouvé ce moyen avec la bibliothèque boost

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

Existe-t-il une autre solution sans cette bibliothèque? J'ai essayé quelque chose comme ça, mais il y a toujours des problèmes avec l'initialisation de la carte.

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
la source
1
Quels sont les problèmes auxquels vous faites allusion? Essayez-vous d'utiliser cette carte à partir d'une autre variable / constante statique globale?
Péter Török le
Ce n'est pas une chaîne de tableau associatif => int, vous mappez un int à un char. v = k + 'a' - 1.
Johnsyweb

Réponses:

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

la source
3
+1 pour plus de simplicité, bien sûr, utiliser un Boost.Assigndesign similaire est assez chouette aussi :)
Matthieu M.
5
+1, merci. Remarque: j'ai dû mettre la ligne d'initialisation dans mon fichier d'implémentation; le laisser dans le fichier d'en-tête m'a donné des erreurs en raison de plusieurs définitions (le code d'initialisation s'exécutait chaque fois que l'en-tête était inclus quelque part).
System.Cats.Lol
1
Avec g ++ v4.7.3, cela compile, jusqu'à ce que j'ajoute cout << A::myMap[1];dans main(). Cela donne une erreur. L'erreur ne se produit pas si je supprime les constqualificatifs, donc je suppose que les cartes ne operator[]peuvent pas gérer un const map, au moins, pas dans l'implémentation g ++ de la bibliothèque C ++.
Craig McQueen
2
L'erreur est: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
En effet, l'opérateur de la carte [] ne peut pas opérer sur une carte const car cet opérateur crée l'entrée référencée si elle n'existe pas (car il renvoie une référence à la valeur mappée). C ++ 11 a introduit la méthode at (KeyValT key) qui vous permet d'accéder à l'élément avec une clé donnée, en lançant une exception si elle n'existe pas. ( en.cppreference.com/w/cpp/container/map/at ) Cette méthode fonctionnera sur les instances const mais ne peut pas être utilisée pour insérer un élément sur une instance non-const (comme le fait l'opérateur []).
mbargiel
108

La norme C ++ 11 a introduit l'initialisation uniforme, ce qui rend cela beaucoup plus simple si votre compilateur la prend en charge:

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


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

Voir aussi cette section de Professional C ++ , sur unordered_maps.

David C. Bishop
la source
Avons-nous besoin du signe égal dans le fichier cpp?
phoad
@phoad: Le signe égal est superflu.
Jinxed
Merci d'avoir montré l'utilisation. Il était vraiment utile de comprendre comment modifier les variables statiques.
User9102d82
12

Je l'ai fait! :)

Fonctionne bien sans 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
la source
11

Si vous trouvez boost::assign::map_list_ofutile, mais que vous ne pouvez pas l'utiliser pour une raison quelconque, vous pouvez écrire le vôtre :

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

Il est utile de savoir comment de telles choses fonctionnent, surtout quand elles sont si courtes, mais dans ce cas, j'utiliserais une fonction:

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

Une approche différente du problème:

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
}

C'est plus efficace, car il n'y a pas de copie de type unique d'une pile à l'autre (y compris le constructeur, les destructeurs sur tous les éléments). Que cela compte ou non dépend de votre cas d'utilisation. Peu importe avec les cordes! (mais vous pouvez ou non trouver cette version "plus propre")

ypnos
la source
3
RVO élimine la copie dans la mienne et la réponse de Neil.
6

Si la mappe ne doit contenir que des entrées connues au moment de la compilation et que les clés de la mappe sont des entiers, vous n'avez pas du tout besoin d'utiliser une mappe.

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
la source
5
+1 pour avoir souligné qu'une carte n'est pas nécessaire, mais vous ne pouvez pas répéter dessus
Viktor Sehr
4
C'est switchhorrible, cependant. Pourquoi pas return key + 'a' - 1?
Johnsyweb
12
@Johnsyweb. Je suppose que la cartographie fournie par l'affiche originale a été présentée uniquement à titre d'exemple et non à titre indicatif de la cartographie réelle dont il dispose. Par conséquent, je suppose également que return key + 'a' - 1cela ne fonctionnerait pas pour sa cartographie réelle.
Matthew T.Staebler
3

Vous pouvez essayer ceci:

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

MaClasse.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

Avec cette implémentation, votre carte statique constante de classes est un membre privé et peut être accessible à d'autres classes à l'aide d'une méthode get publique. Sinon, comme elle est constante et ne peut pas changer, vous pouvez supprimer la méthode publique get et déplacer la variable map dans la section publique des classes. Je laisserais cependant la méthode createMap privée ou protégée si l'héritage et / ou le polymorphisme est requis. Voici quelques exemples d'utilisation.

 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.

J'avais édité mon message original, il n'y avait rien de mal avec le code original dans lequel j'ai posté pour cela compilé, construit et exécuté correctement, c'est juste que ma première version que j'ai présentée comme réponse, la carte a été déclarée comme publique et la carte était const mais n'était pas statique.

Francis Cugler
la source
2

Si vous utilisez un compilateur qui ne prend toujours pas en charge l'initialisation universelle ou si vous avez une réservation dans l'utilisation de Boost, une autre alternative possible serait la suivante

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

Un appel de fonction ne peut pas apparaître dans une expression constante.

essayez ceci: (juste un exemple)

#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
la source
6
Une fonction peut certainement être utilisée pour initialiser un objet const.
Dans le code OP static map<int,int> myMap = create_map();est incorrect.
Prasoon Saurav
3
Le code de la question est faux, nous sommes tous d'accord avec cela, mais cela n'a rien à voir avec les `` expressions constantes '' comme vous le dites dans cette réponse, mais plutôt avec le fait que vous ne pouvez initialiser que les membres statiques constants d'une classe dans le déclaration s'ils sont de type entier ou enum. Pour tous les autres types, l'initialisation doit être effectuée dans la définition du membre et non dans la déclaration.
David Rodríguez - dribeas
La réponse de Neil compile avec g ++. Pourtant, je me souviens avoir eu quelques problèmes avec cette approche dans les versions antérieures de la chaîne d'outils GNU. Existe-t-il une bonne réponse universelle?
Basilevs
1
@Prasoon: Je ne sais pas ce que dit le compilateur, mais l'erreur dans le code de la question initialise un attribut de membre constant de type classe dans la déclaration de classe, que l'initialisation soit une expression constante ou non. Si vous définissez une classe: la struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td;compilation échouera même si l'initialisation est effectuée avec une expression constante ( 5). Autrement dit, «l'expression constante» n'est pas pertinente pour l'exactitude (ou l'absence de celle-ci) du code initial.
David Rodríguez - dribeas
-2

J'utilise souvent ce modèle et je vous recommande de l'utiliser également:

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;

Bien sûr, ce n'est pas très lisible, mais sans autres bibliothèques, c'est mieux que nous puissions faire. De plus, il n'y aura pas d'opérations redondantes comme la copie d'une carte à une autre comme dans votre tentative.

C'est encore plus utile à l'intérieur des fonctions: au lieu de:

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

Utilisez le suivant:

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

Non seulement vous n'avez plus besoin ici de traiter la variable booléenne, mais vous n'aurez plus de variable globale cachée qui est vérifiée si l'initialiseur de la variable statique à l'intérieur de la fonction a déjà été appelé.

Pavel Chikulaev
la source
6
L'héritage devrait être l'outil de dernier recours, pas le premier.
Un compilateur qui prend en charge RVO élimine la copie redondante avec les versions de fonction. La sémantique de déplacement C ++ 0x élimine le reste, une fois qu'ils sont disponibles. En tout cas, je doute que ce soit presque un goulot d'étranglement.
Roger, je connais bien RVO, && et déplacer la sémantique. C'est une solution pour l'instant en quantité minimale de code et d'entités. De plus, toutes les fonctionnalités C ++ 0x n'aideront pas avec un objet statique à l'intérieur d'un exemple de fonction car nous ne sommes pas autorisés à définir des fonctions à l'intérieur de fonctions.
Pavel Chikulaev