2013-05-23 5 views
6

Ho bisogno di una std: struttura dati della mappa che è di sola lettura, il che significa che devo riempirla una volta con i dati e quindi leggere solo quei valori, non cambiarli mai o aggiungerne di nuovi.Come creare una std :: mappa di valori costanti che è ancora accessibile dall'operatore []?

La mia versione non-const assomiglia a questo:

//in .h 
#include <string> 
#include <map> 

std::map<std::string, int> myMap; 
void initMap(); 

//in .cpp 
#include "foo.h" 

void initMap() { 
    myMap["Keys"] = 42; 
} 

Poi io chiamerei initMap() una volta nel mio codice e sia fatta.

Ora ho già letto diverse domande qui e sembra non banale raggiungere la costanza per la mappa.

Facendolo diventare un std::map<std::string, const int> non mi consente di riempirlo nello initMap(). Il riempimento con un temp non costante e il costruttore di copie sulla definizione non funziona, in quanto il costruttore di copie non accetta facilmente la versione non const come input.

Facendolo diventare un const std::map<std::string, int> (che potrei riempire con una copia non costante durante la definizione) disabiliterebbe l'uso dell'operatore [] per l'accesso ai valori.

Quindi c'è un modo per raggiungere (valore) constinità e inizializzare la struttura (preferibilmente nel file di intestazione)?

BTW: Né C++ 0x né C++ 11 né boost:: è un'opzione.

+0

Vuoi "una std: struttura dati mappa di sola lettura" oppure vuoi una mappa con elementi di sola lettura? – juanchopanza

+0

Voglio il primo, ma posso vivere con il successivo se questo mi tiene l'operatore '[]'. –

risposta

13

Non potresti utilizzare il metodo insert() disponibile per std::map?

http://www.cplusplus.com/reference/map/map/insert/

Edit: (soluzione) myMap.insert(std::pair<std::string, const int>("Keys", 42));

Per quanto mi risulta, la ragione per cui questo funziona è perché il costruttore per la coppia, pair (const first_type& a, const second_type& b), inizializza i suoi membri first e second utilizzando i costruttori per first_type e second_type, prendendo a e come rispettivo parametro.

Con la soluzione che stavi cercando di utilizzare, la mia comprensione è che myMap["Keys"] = 42; inizializza il membro second della map (di tipo const int) utilizzando il costruttore predefinito per int. Quindi si tenta di assegnare un valore a quel membro. Poiché questo viene eseguito al di fuori del costruttore della classe map, la dichiarazione const rende impossibile ciò.

Con la soluzione che utilizza insert(), i membri vengono inizializzati nel costruttore di pair. Quindi possono essere dichiarati const. La stessa operazione viene eseguita quando lo pair viene copiato su map.

+0

Espandi la tua risposta in modo molto più fedele e in realtà rispondi alla mia domanda e accetto che sia giusta. Infatti, usando il mio esempio di codice qui sopra, sostituendo 'myMap [" Keys "] = 42;' di 'myMap.insert (std :: make_pair (" Keys ", 42))' funzionerà per un 'std :: map < std :: string, const int> '. Ma non ho idea del motivo per cui questo funziona e l'altro no. –

+0

E ho appena verificato, mi dà correttamente un errore del compilatore sull'accesso in scrittura ('myMap [" Keys "] = 23;') mentre permetto ancora l'accesso in lettura ('std :: cout << myMap [" Keys "]') . –

+0

@ Chaos_99 Mi spiace. Avrei preferito scriverlo come commento, ma non mi è ancora permesso. – Matzar

7

Anche se questo non è possibile per voi, gli altri che vuole fare questo e che hanno un compilatore compatibile con C++ 11, potrebbe usare uniform initialization:

std::map<std::string, const int> myMap = { 
    { "keys", 42 } 
}; 

Oh, e dal modo in cui, don 't definire la mappa nel file di intestazione. Invece dichiarare come extern nel file di intestazione, quindi definirlo nel file di origine.

+0

Ho modificato la domanda originale. No C++ 0x significa anche senza C++ 11. Ma hai assolutamente ragione riguardo alla dichiarazione/definizione. Grazie! –

1

Se il map non può essere modificato, è necessario utilizzare const map<string, int> e non map<string, const int>: la seconda versione consente l'inserimento e la cancellazione di oggetti.

Purtroppo, si dovrà perdere l'operatore []; C++ non ha uno ImmutableMap, o qualcosa del genere. Tuttavia, std::map::at e std::map::find non sono male ...

+0

Se è tra l'operatore [] e l'inserimento/eliminazione, andrei con inserimento/cancellazione. Non usare qualcosa per convenzione è più facile "vendere" ad altri sviluppatori e quindi sintassi di accesso goffo. –

2

La soluzione più semplice è quella di scrivere il proprio, avvolgendo il classe di mappa standard:

template <typename KeyType, typename MappedType, typename CmpType> 
class ConstantMap 
{ 
    typedef std::map<KeyType, MappedType, CmpType> Impl; 
    Impl myImpl; 
public: 
    typedef Impl::value_type value_type; 

    template <ForwardIterator> 
    ConstantMap(ForwardIterator begin, ForwardIterator end, CmpType cmp = CmpType()) 
     : myImpl(begin, end, cmp) 
    { 
    } 

    // necessary if [] is not going to work for missing keys 
    bool contains(KeyType const& key) const 
    { 
     return myImpl.find(key) != myImpl.end(); 
    } 

    MappedType const& operator[](KeyType const& key) const 
    { 
     Impl::const_iterator elem = myImpl.find(key); 
     if (elem == myImpl.end()) { 
      // Not found, do what you want (maybe throw an exception) 
     } 
     return elem.second; 
    } 
}; 

È possibile initialze la mappa passandolo iteratori a una sequenza di tutto ciò che può convertire a value_type.

A seconda delle esigenze, si consiglia di aggiungere ulteriori typedef inoltro, funzioni, ecc Se stai usando C++ 11, si consiglia inoltre di creare un costruttore che può utilizzare un elenco di inizializzazione.

+0

Grazie per il codice di esempio della classe wrapper. Sono sicuro che potrebbe tornare utile a molte persone che cercano una classe di mappe personalizzate. Ma prendo la soluzione di @Matzar fintanto che constare è il mio unico bisogno. –

0

Qualcosa di molto semplice, che ha un ingombro più piccolo ed è più veloce:

static const int MyMapData[] = { 
    42  // index 0 is mapped to "key" 
};

struct MyMap { const int& operator[](std::string key) const { switch (key) { case "Keys": return MyMapData[0];

default: return NotANumber; // Return 0 and raise an assertion, or return "Not a Number". } }

};

Facile da mantenere, nessun uso di modelli, nessun utilizzo di librerie boost e compilabili ovunque.

+0

Potrebbe funzionare per questo semplice esempio, ma l'esecuzione di un'istruzione switch con migliaia di opzioni probabilmente non è più veloce di un accesso std :: map. –