Quali prestazioni possiamo aspettarci da c_str () di std :: string? Tempo sempre costante?


13

Ultimamente ho fatto alcune ottimizzazioni necessarie. Una cosa che ho fatto è cambiare alcuni flussi di ostrings -> sprintfs. Sto scattando un mucchio di stringhe std :: string a array in stile ac, ala

char foo[500];
sprintf(foo, "%s+%s", str1.c_str(), str2.c_str());

Si scopre che l'implementazione std :: string :: c_str () di Microsoft viene eseguita in tempo costante (restituisce solo un puntatore interno). Sembra che libstdc ++ faccia lo stesso . Mi rendo conto che lo std non fornisce garanzie per c_str, ma è difficile immaginare un altro modo di farlo. Se, per esempio, avessero copiato in memoria, avrebbero dovuto allocare memoria per un buffer (lasciandolo al chiamante per distruggerlo - NON parte del contratto STL) O avrebbero dovuto copiare su un statico interno buffer (probabilmente non thread-safe, e non hai garanzie sulla sua durata). Quindi semplicemente restituire un puntatore a una stringa terminata null mantenuta internamente sembra essere l'unica soluzione realistica.

Risposte:


9

Se ricordo, lo standard consente string::c_str()di restituire praticamente tutto ciò che soddisfa:

  • Memoria che è abbastanza grande per il contenuto della stringa e la terminazione NULL
  • Deve essere valido fino a quando non stringviene chiamato un membro non const dell'oggetto specificato

Quindi, in pratica, questo significa un puntatore alla memoria interna; poiché non è possibile tracciare esternamente la durata del puntatore restituito. Penso che la tua ottimizzazione sia sicura supporre che questo sia (piccolo) tempo costante.

In una nota correlata, se la formattazione della stringa limita le prestazioni; potresti trovare più fortuna rimandando la valutazione fino a quando non sarà assolutamente necessario con qualcosa come Boost.Phoenix .

Boost.Format Credo che modifichi la formattazione internamente fino a quando non è richiesto il risultato e puoi utilizzare lo stesso oggetto formato ripetutamente senza ripetere l'analisi della stringa di formato, che ho trovato fare una differenza significativa per la registrazione ad alta frequenza.


2
Potrebbe essere possibile che un'implementazione crei un buffer interno nuovo o secondario - abbastanza grande da aggiungere sul terminatore null. Anche se c_strè un metodo const (o almeno ha un sovraccarico const - dimentico quale), questo non cambia il valore logico, quindi potrebbe essere un motivo mutable. Si sarebbe rompere puntatori da altre chiamate a c_str, salvo che tali indicatori devono fare riferimento alla stessa stringa di logica (quindi non c'è alcun nuovo motivo per riallocare - ci deve essere già un terminatore null) altrimenti non ci deve essere già stato una chiamata a un non -const metodo nel mezzo.
Steve314,

Se questo è veramente valido, le c_strchiamate possono essere O (n) per la riallocazione e la copia. Ma è anche possibile che ci siano regole extra nello standard di cui non sono a conoscenza che impedirebbero questo. Il motivo per cui suggerisco - chiamate c_strnon sono veramente destinata a essere comune per quanto ne so, quindi non può essere considerato importante per garantire che stanno rapidamente - evitando che byte extra di archiviazione per un terminatore null normalmente non necessaria in stringcasi che non uso c_strpuò hanno avuto la precedenza.
Steve314,

Boost.Formatinternamente attraversa flussi che internamente sprintffiniscono con un sovraccarico piuttosto grande. La documentazione dice che è circa 8 volte più lenta del semplice sprintf. Se desideri prestazioni e sicurezza del tipo, prova Boost.Spirit.Karma.
Jan Hudec,

Boost.Spirit.Karmaè un buon consiglio per le prestazioni, ma attenzione che ha una metodologia molto diversa che può essere complicata per adattare il printfcodice (e i programmatori) di stile esistente . In gran parte mi sono bloccato Boost.Formatperché il nostro I / O è asincrono; ma un grande fattore è che posso convincere i miei colleghi a usarlo in modo coerente (consente ancora qualsiasi tipo con un ostream<<sovraccarico - che accompagna bene il .c_str()dibattito) I numeri delle prestazioni Karma .
valore

23

Nello standard c ++ 11 (sto leggendo la versione N 3290), il capitolo 21.4.7.1 parla del metodo c_str ():

const charT* c_str() const noexcept; const charT* data() const noexcept;

Restituisce: un puntatore p tale che p + i == & operatore per ogni i in [0, size ()].
Complessità: tempo costante.
Richiede: il programma non deve alterare nessuno dei valori memorizzati nella matrice di caratteri.

Quindi sì: la complessità temporale costante è garantita dallo standard.

Ho appena controllato lo standard c ++ 03 e non ha tali requisiti, né racconta la complessità.


8

In teoria C ++ 03 non lo richiede, e quindi la stringa può essere una matrice di caratteri in cui viene aggiunta la presenza del terminatore null proprio nel momento in cui viene chiamato c_str (). Ciò può richiedere una riallocazione (non viola la costanza, se il puntatore privato interno viene dichiarato come mutable).

C ++ 11 è più rigoroso: richiede costanza di tempo, quindi non è possibile eseguire il riposizionamento e l'array deve essere sempre abbastanza largo da archiviare il null alla fine. c_str (), da solo, può ancora fare " ptr[size()]='\0'" per assicurarsi che il nulla sia realmente presente. Non viola la costanza dell'array poiché l'intervallo [0..size())non viene modificato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.