Come inizializzare una mappa const statica privata in C ++?


108

Ho bisogno solo di un dizionario o di un array associativo string=> int.

C'è la mappa dei tipi C ++ per questo caso.

Ma ho bisogno di una sola mappa per tutte le istanze (-> statica) e questa mappa non può essere modificata (-> const);

Ho trovato questo modo con la libreria boost

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

C'è un'altra soluzione senza questa libreria? Ho provato qualcosa di simile, ma ci sono sempre alcuni problemi con l'inizializzazione della mappa.

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

};

1
Quali sono i problemi a cui ti riferisci? Stai cercando di utilizzare questa mappa da un'altra variabile / costante statica globale?
Péter Török

Non è un array associativo string => int, stai mappando un int a un char. v = k + 'a' - 1.
Johnsyweb

Risposte:


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

3
+1 per semplicità, ovviamente anche l'uso di un Boost.Assigndesign simile è abbastanza carino :)
Matthieu M.

5
+1, grazie. Nota: ho dovuto inserire la riga di inizializzazione nel mio file di implementazione; lasciarlo nel file di intestazione mi dava errori a causa di più definizioni (il codice di inizializzazione veniva eseguito ogni volta che l'intestazione veniva inclusa da qualche parte).
System.Cats.Lol

1
Con g ++ v4.7.3, questo viene compilato, finché non aggiungo cout << A::myMap[1];in main(). Dà un errore. L'errore non si verifica se rimuovo i constqualificatori, quindi immagino che le mappe operator[]non possano gestire const map, almeno, non nell'implementazione g ++ della libreria C ++.
Craig McQueen

2
L'errore è: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 effetti, l'operatore della mappa [] non può operare su una mappa const perché quell'operatore crea la voce referenziata se non esiste (poiché restituisce un riferimento al valore mappato). C ++ 11 ha introdotto il metodo at (KeyValT key) che consente di accedere all'elemento con una determinata chiave, generando un'eccezione se non esiste. ( en.cppreference.com/w/cpp/container/map/at ) Questo metodo funzionerà su istanze const ma non può essere utilizzato per inserire un elemento su un'istanza non const (come fa l'operatore []).
mbargiel

108

Lo standard C ++ 11 ha introdotto l'inizializzazione uniforme che lo rende molto più semplice se il tuo compilatore lo supporta:

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


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

Vedi anche questa sezione da Professional C ++ , su unordered_maps.


Abbiamo davvero bisogno del segno di uguale nel file cpp?
phoad

@phoad: il segno di uguale è superfluo.
Jinxed

Grazie per aver mostrato l'utilizzo. È stato davvero utile capire come modificare le variabili statiche.
User9102d82

12

L'ho fatto! :)

Funziona bene senza 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);

11

Se trovi boost::assign::map_list_ofutile, ma non puoi usarlo per qualche motivo, potresti scrivere il tuo :

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

È utile sapere come funzionano queste cose, specialmente quando sono così brevi, ma in questo caso userei una funzione:

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

6

Un approccio diverso al problema:

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
}

Questo è più efficiente, poiché non esiste una copia di un tipo dallo stack all'heap (inclusi il costruttore, i distruttori su tutti gli elementi). Se questo è importante o meno dipende dal tuo caso d'uso. Non importa con le stringhe! (ma potresti o meno trovare questa versione "più pulita")


3
RVO elimina la copia nella mia e nella risposta di Neil.

6

Se la mappa deve contenere solo voci note al momento della compilazione e le chiavi della mappa sono numeri interi, non è necessario utilizzare una mappa.

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

5
+1 per aver sottolineato che una mappa non è necessaria, tuttavia, non è possibile iterare su questo
Viktor Sehr

4
È switchorribile, però. Perché no return key + 'a' - 1?
Johnsyweb

12
@Johnsyweb. Presumo che la mappatura fornita dal poster originale sia stata presentata esclusivamente come un esempio e non indicativa della mappatura effettiva che ha. Pertanto, presumo anche che return key + 'a' - 1non funzionerebbe per la sua mappatura effettiva.
Matthew T. Staebler

3

Potresti provare questo:

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

Con questa implementazione, la mappa statica costante delle classi è un membro privato e può essere accessibile ad altre classi utilizzando un metodo get pubblico. Altrimenti poiché è costante e non può cambiare, è possibile rimuovere il metodo get public e spostare la variabile map nella sezione public delle classi. Tuttavia lascerei il metodo createMap privato o protetto se è richiesta l'ereditarietà e / o il polimorfismo. Ecco alcuni esempi di utilizzo.

 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.

Avevo modificato il mio post originale, non c'era niente di sbagliato nel codice originale in cui l'ho pubblicato compilato, costruito ed eseguito correttamente, era solo che la mia prima versione che ho presentato come risposta la mappa è stata dichiarata pubblica e la mappa è stata const ma non era statico.


2

Se stai usando un compilatore che ancora non supporta l'inizializzazione universale o hai una prenotazione nell'usare Boost, un'altra possibile alternativa sarebbe la seguente

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

0

Una chiamata di funzione non può apparire in un'espressione costante.

prova questo: (solo un esempio)

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

6
Una funzione può certamente essere utilizzata per inizializzare un oggetto const.

Nel codice di OP static map<int,int> myMap = create_map();non è corretto.
Prasoon Saurav

3
Il codice nella domanda è sbagliato, siamo tutti d'accordo, ma non ha nulla a che fare con le 'espressioni costanti' come dici in questa risposta, ma piuttosto con il fatto che puoi inizializzare solo i membri statici costanti di una classe nel dichiarazione se sono di tipo intero o enum. Per tutti gli altri tipi, l'inizializzazione deve essere eseguita nella definizione del membro e non nella dichiarazione.
David Rodríguez - dribeas

La risposta di Neil viene compilata con g ++. Tuttavia, ricordo di avere alcuni problemi con questo approccio nelle versioni precedenti della toolchain GNU. Esiste una risposta giusta universale?
Basilevs

1
@Prasoon: non so cosa dice il compilatore, ma l'errore nel codice della domanda è l'inizializzazione di un attributo membro costante di tipo classe nella dichiarazione della classe, indipendentemente dal fatto che l'inizializzazione sia o meno un'espressione costante. Se definisci una classe: struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td;non verrà compilata anche se l'inizializzazione viene eseguita con un'espressione costante ( 5). Cioè, "espressione costante" è irrilevante per la correttezza (o la mancanza di essa) del codice iniziale.
David Rodríguez - dribeas

-2

Uso spesso questo schema e ti consiglio di usarlo anche:

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;

Certo non è molto leggibile, ma senza altre librerie è meglio che possiamo fare. Inoltre non ci saranno operazioni ridondanti come la copia da una mappa all'altra come nel tuo tentativo.

Questo è ancora più utile all'interno delle funzioni: invece di:

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

Utilizza il seguente:

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

Non solo non è più necessario che tu abbia a che fare con la variabile booleana, non avrai più la variabile globale nascosta che viene controllata se l'inizializzatore della variabile statica all'interno della funzione è già stato chiamato.


6
L'eredità dovrebbe essere lo strumento di ultima istanza, non il primo.

Un compilatore che supporta RVO elimina la copia ridondante con le versioni della funzione. La semantica di spostamento di C ++ 0x elimina il resto, una volta disponibili. In ogni caso, dubito che sia vicino a essere un collo di bottiglia.

Roger, conosco bene RVO, && e sposta la semantica. Questa è una soluzione per ora in quantità minima di codice ed entità. Inoltre, tutte le funzionalità di C ++ 0x non saranno di aiuto con gli oggetti statici all'interno di esempi di funzioni poiché non ci è permesso definire funzioni all'interno di funzioni.
Pavel Chikulaev
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.