2012-12-14 12 views
5

considerare questo programma:elementi Inserire le std :: map senza copiare in più

#include <map> 
#include <string> 
#define log magic_log_function // Please don't mind this. 

// 
// ADVENTURES OF PROGO THE C++ PROGRAM 
// 

class element; 
typedef std::map<int, element> map_t; 

class element { 
public: 
    element(const std::string&); 
    element(const element&); 
    ~element(); 
    std::string name; 
}; 
element::element(const std::string& arg) 
    : name(arg) 
{ 
    log("element ", arg, " constucted, ", this); 
} 
element::element(const element& other) 
    : name(other.name) 
{ 
    name += "-copy"; 
    log("element ", name, " copied, ", this); 
} 
element::~element() 
{ 
    log("element ", name, " destructed, ", this); 
} 
int main(int argc, char **argv) 
{ 
    map_t map1; element b1("b1"); 
    log(" > Done construction."); 
    log(" > Making map 1."); 
    map1.insert(std::pair<int, element>(1, b1)); 
    log(" > Done making map 1."); 
    log(" > Before returning from main()"); 
} 

Esso crea alcuni oggetti sulla pila e insert li s in un contenitore std::map, creando due copie extra temporanei nel processo:

element b1 constucted, 0x7fff228c6c60 
> Done construction. 
> Making map 1. 
element b1-copy copied, 0x7fff228c6ca8 
element b1-copy-copy copied, 0x7fff228c6c98 
element b1-copy-copy-copy copied, 0x232d0c8 
element b1-copy-copy destructed, 0x7fff228c6c98 
element b1-copy destructed, 0x7fff228c6ca8 
> Done making map 1. 
> Before returning from main() 
element b1 destructed, 0x7fff228c6c60 
element b1-copy-copy-copy destructed, 0x232d0c8 

possiamo sbarazzarci di una chiamata al costruttore copia in più cambiando la firma std::pair-std::pair<int, element&>, tuttavia, il secondo è ancora provvisorio cr eated e immediatamente distrutte:

element b1 constucted, 0x7fff0fe75390 
> Done construction. 
> Making map 1. 
element b1-copy copied, 0x7fff0fe753c8 
element b1-copy-copy copied, 0x1bc4098 
element b1-copy destructed, 0x7fff0fe753c8 
> Done making map 1. 
> Before returning from main() 
element b1 destructed, 0x7fff0fe75390 
element b1-copy-copy destructed, 0x1bc4098 

C'è un modo per rendere std::map basta prendere un oggetto sulla pila per riferimento e fare una singola copia interna di esso?

+3

Il modo più efficiente sarebbe 'map1.emplace (1," b1 ");'. Ma non è ancora supportato ovunque. La cosa migliore è 'map1 [1] =" b1 ";', sebbene abbia una semantica leggermente diversa. –

+1

Si noti inoltre che 'map_t :: value_type' è ** not **' std :: pair '! –

+2

'#include" log.cpp "' è * davvero * spaventoso. –

risposta

4

La pratica standard (con versioni precedenti di C++) in cui sono stato è utilizzare una mappa di puntatori condivisi.

Ancora crea una copia dei puntatori condivisi, ma di solito è molto meno oneroso della copia di oggetti di grandi dimensioni.

+0

Grazie per il suggerimento, la modifica del codice per utilizzare shared_ptrs è stata un miglioramento, in termini di prestazioni. –

3

È possibile utilizzare emplace():

L'elemento è costruito sul posto, vale a dire senza copiare o spostare le operazioni vengono eseguite. Il costruttore del tipo di elemento (value_type, vale a dire std :: coppia) viene chiamato con esattamente gli stessi argomenti forniti alla funzione

+3

g ++ da gcc 4.7.2 non ce l'ha –

-1

Beh, se non avete emplace, è possibile costruire l'elemento sul heap e passa puntatori per mappare:

typedef std::map<int, element*> map_t; 
... 
printf(" > Making pair 1.\n"); 
std::pair<int, element*> pair(1, new element ("b1")) ; 
printf(" > Making map 1.\n"); 
map1.insert(pair); 

ma poi si sono soggette a memoria-perdite se non prestare attenzione quando la vostra mappa lascia spazio ...

+0

Grazie per l'input, ma voglio std :: map per gestire gli oggetti element, che non è il caso in cui sono memorizzati i puntatori. –

+0

@Mirscha Questo capisco perfettamente ... – pbhd

8

Questo è uno dei tanti casi d'uso che hanno motivato C++11 's move funzionalità, supportata da una serie di nuove caratteristiche, riferimenti particolarmente rvalue, e una varietà di nuove interfacce della libreria standard, tra cui std::map::emplace, std::vector::emplace_back, ecc

Se, per qualsiasi motivo, non è possibile ancora utilizzare C++11, si può almeno consolarsi con il pensiero che il problema è stato riconosciuto, e che una soluzione è stata standardizzata e implementata, e che inoltre molti di noi lo stanno utilizzando, alcuni di noi [1] nel codice di produzione. Quindi, come dice il vecchio scherzo, a solution exists ed è la tua richiesta quando lo fai.

Nota che non è necessario utilizzare la funzione membro emplace se gli oggetti implementano i costruttori di movimento, che potrebbero anche eseguire per impostazione predefinita. Ciò non accadrà se i costruttori di copie sono espliciti, quindi il test precedente potrebbe produrre effetti di osservatore (e in effetti potrebbe anche sopprimere le ottimizzazioni del compilatore nel caso dei POD, quindi anche con C++ 03 potresti non avere il problema pensi di farlo).

Ci sono una varietà di hack disponibili che in genere evitano le copie con solo "minori" modifiche al codice sorgente, ma IMHO l'approccio migliore è quello di iniziare a spostarsi verso C++ 11. Qualunque cosa tu faccia, cerca di farlo in un modo che renderà l'inevitabile migrazione meno dolorosa.


[Nota 1]: Disclaimer: scrivo non è più il codice di produzione, avendo più o meno in pensione, quindi non sono parte del "alcuni di noi" in questa frase.

+0

Grazie per l'ottima risposta. Purtroppo non usiamo ancora C++ 11, quindi per ora ho dovuto accontentarmi di shared_ptrs di boost. –

+0

@MischaArefiev, abbastanza giusto. Ero in quella posizione anche nel mio ultimo lavoro, e ora che sono libero di usare C++ 11, mi rendo conto di quanto dolore ho passato perché non potevo farlo prima. Ma la migrazione è un compito enorme, quindi non sto criticando la decisione di non usarlo "ancora". Aggiunta una frase alla mia risposta, comunque ... – rici

Problemi correlati