2016-05-04 44 views
13

Forse è solo la mancanza di caffè, ma sto cercando di creare un std::string da un array char con terminazione nullo con una lunghezza massima nota e io no sapere, come farloCopia null char array terminato su std :: string rispettando la lunghezza del buffer

auto s = std::string(buffer, sizeof(buffer)); 

.. era il mio candidato preferito, ma visto che il C++ le stringhe non sono terminazione null questo comando copierà sizeof(buffer) byte, indipendentemente da qualsiasi contenuto '\ 0'.

auto s = std::string(buffer); 

.. copie da buffer fino \0 è trovato. Questo è quasi quello che voglio, ma non posso fidarmi del buffer di ricezione, quindi mi piacerebbe fornire una lunghezza massima.

Naturalmente, ora posso integrare strnlen() così:

auto s = std::string(buffer, strnlen(buffer, sizeof(buffer))); 

ma che sembra sporca - attraversa il buffer di due volte e ho a che fare con il C-manufatti come string.h e strnlen() (ed è brutto) .

Come faccio nel moderno C++?

+4

È terminato da null o ha lunghezza esattamente di sizeof (buffer), non può essere entrambi allo stesso tempo. –

+1

Non è vero (e sono sorpreso per l'upvote). Un C-buffer fornito a qualsiasi tipo di funzione di scrittura può essere pre-allocato con una dimensione fissa. La sequenza scritta nel buffer può ancora essere terminata con null. – frans

+0

Non è vero se è possibile adattare una stringa N di lunghezza N terminata o più in un buffer di N byte, per alcuni N. Avete un esempio funzionante? –

risposta

19
const char* end = std::find(buffer, buffer + sizeof(buffer), '\0'); 
std::string s(buffer, end); 
+0

Non attraversa ancora il buffer due volte? – songyuanyao

+3

@songyuanyao: a seconda di come lo si guarda, sì (un attraversamento per trovare la lunghezza, quindi uno per copiare i byte). Ma questo è inevitabile perché semplicemente non puoi sapere la dimensione corretta da allocare finché non controlli la lunghezza e non puoi copiare i byte prima di assegnarli. Una vera soluzione single-pass alloca sempre 'sizeof (buffer)' indipendentemente dalla brevità del contenuto reale, il che può aumentare significativamente il consumo di memoria. –

+4

O per dirla in un altro modo, 'std :: string s (buffer);' attraversa il buffer già due volte e il limite di dimensioni aggiuntive risulta non utile a meno che non ci occupiamo di sovra-allocazione. In linea di principio potresti creare una serie di allocazioni di dimensioni esponenzialmente crescenti (come un 'vector'), ma nel momento in cui aggiungi tutte le copie intermedie hai ancora attraversato i dati circa due volte. –

1

Qualcosa di simile potrebbe funzionare in un unico passaggio ..

auto eos = false; 
std::string s; 
std::copy_if(buffer, buffer + sizeof(buffer), std::back_inserter(s), 
    [&eos](auto v) { 
    if (!eos) { 
     if (v) { 
     return true; 
     } 
     eos = true; 
    } 
    return false; 
    }); 
+3

Tranne i passaggi aggiuntivi necessari per copiare i caratteri sulla riallocazione? –

+0

@ T.C senza sapere quanto grande 'buffer' è, è forse difficile indovinare se ci saranno eventuali riallocazioni? – Nim

1

Se si desidera una soluzione single-pass, iniziare con questo:

template<class CharT> 
struct smart_c_string_iterator { 
    using self=smart_c_string_iterator; 
    std::size_t index = 0; 
    bool is_end = true; 
    CharT* ptr = nullptr; 
    smart_c_string_iterator(CharT* pin):is_end(!pin || !*pin), ptr(pin) {} 
    smart_c_string_iterator(std::size_t end):index(end) {} 
}; 

ora, gussy in su e renderlo un iteratore di accesso casuale completo. La maggior parte delle operazioni è davvero semplice (++ ecc. Devono essere eseguite entrambe le versioni ptr e index), eccetto== e !=.

friend bool operator==(self lhs, self rhs) { 
    if (lhs.is_end&&rhs.is_end) return true; 
    if (lhs.index==rhs.index) return true; 
    if (lhs.ptr==rhs.ptr) return true; 
    if (lhs.is_end && rhs.ptr && !*rhs.ptr) return true; 
    if (rhs.is_end && lhs.ptr && !*lhs.ptr) return true; 
    return false; 
} 
friend bool operator!=(self lhs, self rhs) { 
    return !(lhs==rhs); 
} 

abbiamo anche bisogno di:

template<class CharT> 
std::pair<smart_c_string_iterator,smart_c_string_iterator> 
smart_range(CharT* ptr, std::size_t max_length) { 
    return {ptr, max_length}; 
} 

ora facciamo questo:

auto r = smart_range(buffer, sizeof(buffer)); 
auto s = std::string(r.first, r.second); 

e ad ogni passo, controlliamo sia per la lunghezza del buffer e la terminazione nullo quando si fa la copia.

Ora, Ranges v3 determina il concetto di una sentinella, che consente di fare qualcosa di simile a quanto sopra con costi di runtime ridotti. Oppure puoi realizzare a mano la soluzione equivalente.