2009-12-20 5 views
29

Ho una tabella dei simboli implementata come std::map. Per il valore, non esiste un modo per costruire legittimamente un'istanza del tipo di valore tramite un costruttore predefinito. Tuttavia, se non fornisco un costruttore predefinito, ottengo un errore del compilatore e se faccio in modo che il costruttore asserisca, il mio programma si compila bene ma si blocca all'interno di map<K,V>::operator [] se provo a usarlo per aggiungere un nuovo membro.Utilizzo di std :: map <K,V> dove V non ha un costruttore predefinito utilizzabile

Esiste un modo per impedire a C++ di disabilitare map[k] come valore l in fase di compilazione (pur consentendo come valore r)?


BTW: so di poter inserire nella mappa utilizzando Map.insert(map<K,V>::value_type(k,v)).


Edit: diverse persone hanno proposto una soluzione che ammontano a modificare il tipo di valore in modo che la mappa può costruire uno senza chiamare il costruttore di default. Questo ha esattamente il risultato opposto di quello che voglio perché nasconde l'errore fino a tardi. Se fossi disposto a farlo, potrei semplicemente rimuovere l'asserzione dal costruttore. Che cosa I Desidera è che l'errore si verifichi ancora prima; al momento della compilazione. Tuttavia, sembra che non ci sia modo di distinguere tra gli usi r-value e l-value di operator[], quindi sembra che ciò che voglio non possa essere fatto quindi dovrò solo fare a meno di usarlo tutto insieme.

risposta

31

Non è possibile effettuare il compilatore distinguere tra i due usi di operatore [], perché sono la stessa cosa. Operatore [] restituisce un riferimento, quindi la versione dell'assegnazione sta semplicemente assegnando a quel riferimento.

Personalmente, non ho mai utilizzare operatore [] per le mappe per qualsiasi cosa, ma il codice demo veloce e sporco. Utilizzare invece insert() e find(). Si noti che il make_pair() funzione rende inserire più facile da usare:

m.insert(make_pair(k, v)); 

In C++ 11, si può anche fare

m.emplace(k, v); 
m.emplace(piecewise_construct, make_tuple(k), make_tuple(the_constructor_arg_of_v)); 

anche se il costruttore di copia/spostamento non è in dotazione .

+0

Se si dispone di C++ 11 o versioni successive, si consiglia di utilizzare un elenco di inizializzazione: 'm.insert ({k, v});'. Usa 'V map :: at (tasto K)' per recuperare il valore, ad es. 'int val = m.at (" important_value ")' – CJxD

1

È un po 'brutto, ma un modo per ovviare a questo è aggiungere una variabile membro che tenga traccia se un'istanza è valida o meno. Il costruttore predefinito contrassegna un'istanza come non valida, ma tutti gli altri costruttori contrassegnano l'istanza come valida.

Assicurarsi che l'operatore di assegnazione trasferisca correttamente la nuova variabile membro.

Modifica il distruttore per ignorare le istanze non valide.

Modifica tutte le altre funzioni membro per lanciare/errori/asserire quando operano su un'istanza non valida.

È quindi possibile utilizzare l'oggetto in una mappa e fintanto che si utilizzano solo oggetti correttamente costruiti, il codice funzionerà correttamente.

Ancora una volta, questa soluzione è utile se si desidera utilizzare la mappa STL e non si desidera utilizzare insert e find anziché operator [].

+0

Tutto ciò che fa è ritardare il problema. Voglio far apparire il problema ancora prima. Come succede, non ho bisogno di un flag come oggetto predefinito seg-v quando lo provi e lo usi. – BCS

0

Quando si utilizza l'override di un operatore in C++, è preferibile attenersi il più strettamente possibile alla semantica dell'operatore nel caso predefinito. La semantica del default. operator [] è la sostituzione di un membro esistente in un array. Sembrerebbe che std :: map pieghi un po 'le regole. Questo è un peccato, perché porta a questa sorta di confusione.

Si noti che la documentazione (http://www.sgi.com/tech/stl/Map.html) per l'operatore [] in std :: map dice: "Restituisce un riferimento all'oggetto associato a una determinata chiave.Se la mappa non contiene già tale oggetto, operatore [ ] inserisce l'oggetto predefinito data_type(). "

Suggerirei di trattare la sostituzione e l'inserimento in modo diverso. Sfortunatamente, questo significa che devi sapere quale è richiesto. Ciò potrebbe significare fare prima una ricerca sulla mappa. Se le prestazioni sono un problema, potrebbe essere necessario trovare un'ottimizzazione in cui è possibile testare l'appartenenza e inserire con una sola ricerca.

5

tuo V non ha un costruttore di default, quindi non si può davvero aspettarsi std::map<K,V>std::map<K,V>::operator[] per essere utilizzabili.

Un std::map<K, boost::optional<V> >fa hanno un mapped_type che è default-costruibile, e probabilmente ha la semantica che si desidera. Fare riferimento alla documentazione Boost.Optional per i dettagli (il numero deve essere a conoscenza di essi).

+2

È perfettamente OK utilizzare tipi costruttivi non predefiniti con std :: map - non è possibile utilizzare l'operatore []. –

+0

Vero, grazie! Incorporato nel testo. – ariels

4

Se il valore-tipo non è default-costruibile, quindi operator[] semplicemente non funzionerà per voi.

Cosa si può fare, però, è quello di fornire funzioni liberi che ottenere e impostare i valori in una mappa per convenienza.

Esempio:

template <class K, class V> 
V& get(std::map<K, V>& m, const K& k) 
{ 
    typename std::map<K, V>::iterator it = m.find(k); 
    if (it != m.end()) { 
     return it->second; 
    } 
    throw std::range_error("Missing key"); 
} 

template <class K, class V> 
const V& get(const std::map<K, V>& m, const K& k) 
{ 
    typename std::map<K, V>::const_iterator it = m.find(k); 
    if (it != m.end()) { 
     return it->second; 
    } 
    throw std::range_error("Missing key"); 
} 

template <class K, class V> 
void set(std::map<K, V>& m, const K& k, const V& v) 
{ 
    std::pair<typename std::map<K, V>::iterator,bool> result = m.insert(std::make_pair(k, v)); 
    if (!result.second) { 
     result.first->second = v; 
    } 
} 

Si potrebbe anche considerare un getter come dict.get(key [, default]) in Python (che restituisce il valore predefinito fornito se la chiave non è presente (ma che ha un problema di usabilità in quanto il default ha sempre da costruire , anche se sai che la chiave è sulla mappa)

+0

re: il default sempre in costruzione, questo è ciò che delega, valutazione pigro e lambda sono per :) – BCS

+0

In C++ 11, 'V map :: at (tasto K)' funziona a meraviglia. Salva ottenendo un iteratore e facendo controlli. – CJxD

0

potresti specializzare std :: map per il tuo tipo di valore Non sto dicendo che sia una buona idea, ma può essere fatto. Mi sono specializzato scoped_ptr<FILE> ' sdor to fclose anziché delete.

Qualcosa di simile:

template<class K, class Compare, class Allocator> 
my_value_type& std::map<K,my_value_type,Compare,Allocator>::operator[](const K& k) 
{ 
    //... 
} 

Ciò dovrebbe consentire di inserire il codice che si desidera in operatore [] per il vostro tipo. Sfortunatamente, non conosco un modo in C++ corrente per restituire solo valori r. In C++ 0x si potrebbe essere in grado di utilizzare:

template<class K, class Compare, class Allocator> 
my_value_type&& std::map<K,my_value_type,Compare,Allocator>::operator[](const K& k) 
{ 
    //... 
} 

Ciò restituirà un riferimento R-valore (& &).

1

Non certo perché si compila per te, penso che il compilatore dovrebbe avere catturato il vostro costruttore mancante.

cosa sull'utilizzo

map<K,V*> 

invece di

map<K,V> ? 
+0

migliore della mappa sarebbe la mappa > –

+0

Senza il costruttore, non viene compilato. Per quanto riguarda l'uso di V *, sarebbe controproducente in quanto spingerebbe il rilevamento degli errori fino a tardi e sto cercando di farlo accadere prima. Quello che sto cercando di fare è compilare il codice per i casi che non chiameranno mai il costruttore predefinito e non compileranno i casi che potrebbero/potrebbero chiamarlo. – BCS

+0

quello di cui hai bisogno sarebbe generare solo codice parziale, in base a ciò di cui hai effettivamente bisogno. Non penso che nessun compilatore supporti questo. quando viene generato un modello, viene creato l'intero codice, non solo i bit che si utilizzano. –

3

derivare una nuova classe da std::map<K,V> e creare il proprio operator[]. È necessario restituire un riferimento const, che non può essere utilizzato come valore l.

+0

Oh, 'const' che potrebbe funzionare! – BCS

+4

std :: map non ha un distruttore virtuale quindi è una cattiva pratica derivare da esso –

+2

@Jacek, purché la classe derivata non introduca nuovi membri di dati e il proprio distruttore sia vuoto, è sicuro. –

2

Utilizzare map<K,V>::at(). map<K,V>::operator [] proverà a predefinire-costruire un elemento se la chiave fornita non esiste già.

+0

semplice, pulito e funziona perfettamente – DomTomCat