2015-02-22 15 views
6

Qual è il modo corretto di C++ 11 per estrarre un set di caratteri da un stringing senza usare boost?Come ottenere caratteri da Stringstream senza copiare?

Voglio farlo senza copiare, se possibile, perché dove viene utilizzato è in un ciclo di dati critico. Sembra, tuttavia, std :: string non consenta l'accesso diretto ai dati.

Ad esempio, il codice di seguito esegui una copia sottostringa su uno stringstream:

inline std::string left(std::stringstream ss, uint32_t count) { 
    char* buffer = new char[count]; 
    ss.get(buffer, count); 
    std::string str(buffer); // Second copy performed here 
    delete buffer; 
    return str; 
} 
  1. Dovrei essere anche usare char * buffer avviene C++ 11?
  2. Come faccio a girare una seconda copia?

La mia comprensione è che i vettori inizializzano ogni carattere, quindi voglio evitarlo.

Inoltre, questo deve essere passato in una funzione che accetta const char *, quindi ora dopo questa esecuzione sono costretto a fare un .c_str(). Ne fa anche una copia?

Sarebbe bello poter restituire un const char *, ma questo sembra andare contro lo stile "corretto" di C++ 11.

di capire quello che sto cercando di fare, qui è "effettivamente" quello che voglio usarlo per:

fprintf(stderr, "Data: [%s]...", left(ststream, 255)); 

Ma la C++ 11 forze:

fprintf(stderr, "Data: [%s]...", left(str_data, 255).c_str()); 

Quanti copie di quella stringa sto facendo qui?

Come posso ridurlo a una singola copia fuori dallo stringstream?

+0

'std :: string str (buffer);' causa un comportamento indefinito, 'buffer' non è terminato da null –

+0

hai considerato di passare' ss' per riferimento const anziché per valore? Ciò eliminerebbe una copia. –

+0

dare un 'std :: string' a' fprintf' è un comportamento indefinito in tutte le versioni di C++; questo non è stato modificato da C++ 11 –

risposta

7

Si potrebbe usare qualcosa come descritto in questo link: How to create a std::string directly from a char* array without copying?

In sostanza, creare una stringa, chiamare il metodo di ridimensionamento() sulla stringa con la dimensione che viene passato alla funzione e quindi passare il puntatore al primo carattere della stringa al metodo stringstring.get(). Finirai con una sola copia.

inline std::string left(std::stringstream& ss, uint32_t count) { 
    std::string str; 
    str.resize(count); 
    ss.get(&str[0], count); 
    return str; 
} 
+1

Vorrei anche suggerire di passare lo stringstream in riferimento. Non conosciamo la lunghezza dei dati in stringstream e copiando quella quantità di dati potrebbe essere il collo di bottiglia stesso. Inoltre, osserverei che restituire la variabile creata atomicamente dovrebbe spostare la stringa e copiarla fintanto che questa viene compilata con C++ 11 o flag equivalenti. – Freddy

+0

Buon punto: lo stream deve essere passato per riferimento. Tuttavia, il valore di ritorno non verrà mai copiato, poiché ogni compilatore utilizzerà comunque l'ottimizzazione del valore di ritorno. – fvannee

+0

Sì, in realtà ho usato shared_ptr sullo stream (non l'ho fatto nel mio codice di esempio). – user3072517

2

Il mio suggerimento:

  1. Creare il std::string da restituire dandogli la dimensione.

  2. Leggere i caratteri uno a uno dal numero stringstream e impostare i valori nel numero std::string.

Ecco cosa la funzione è simile:

inline std::string left(std::stringstream ss, uint32_t count) { 
    std::string str(count+1, '\0'); 
    for (uint32_t i = 0; i < count; ++i) 
    { 
     int c = ss.getc(); 
     if (c != EOF) 
     { 
      str[i] = c; 
     } 
     else 
     { 
      break; 
     } 
    } 
    return str; 
} 
2

R Sahu, questo mi piace! Ovvio ora che lo vedo fatto.;-)

ho un mod se (così come approvato una shared_ptr di flusso che è quello che ho effettivamente avuto nella mia versione):

Nella tua inizializzatore, si sta riempiendo con i null. Hai solo bisogno di riempire con l'ultimo, quindi vi propongo un tweak di questo:

inline std::string left(std::shared_ptr<std::stringstream> ss, uint32_t count) { 
    std::string str; 
    str.reserve(count + 1); 
    uint32_t i; 
    for(i = 0; i < count; ++i) { 
     int c = ss->get(); 
     if(c != EOF) { 
      str[i] = c; 
     } else { 
      break; 
     } 
    } 
    str[i] = '\0'; 
    return str; 
} 

Ora, inizializzato solo con valori nulli su un singolo carattere.

Grazie R Sahu!

+0

La classe stringa non ha un costruttore che accetta solo un numero intero come parametro. Devi sempre specificare anche il carattere per riempire la stringa. – fvannee

+2

perché aggiungere un 'shared_ptr' quando puoi semplicemente usare un riferimento? –

+0

Perché un riferimento continua a copiare. Ho fatto alcuni test delle prestazioni e in tutti i casi shared_ptr garantisce una copia puntatore, ma il riferimento no. Nel mio caso, ha conservato copie interne. – user3072517

0

Se lo scopo di questa funzione è solo per il passaggio ad fprintf o un altro flusso in stile C, allora si potrebbe evitare l'allocazione completamente nel modo seguente:

void left(FILE *out, std::stringstream &in, size_t count) 
{ 
    in.seekg(0); 
    char ch; 
    while (count-- && in.get(ch)) 
     fputc(out, static_cast<unsigned char>(ch)); 
} 

Usage:

fprintf(stderr, "Data: ["); 
left(stderr, stream, 255); 
fprintf(stderr, "] ...\n"); 

Tenere presente che sarà necessario un altro seekg se si tenta di utilizzare le funzioni di lettura del flusso sullo stringstream in un secondo momento; e non mi sorprenderebbe se questa fosse la stessa velocità o più lenta delle opzioni che coinvolgono str().

Problemi correlati