Quando dovrei usare string_view in un'interfaccia?


16

Sto usando una libreria interna progettata per imitare una libreria C ++ proposta e in alcuni anni ho visto la sua interfaccia cambiata dall'uso std::stringa string_view.

Quindi cambio doverosamente il mio codice, per adeguarmi alla nuova interfaccia. Sfortunatamente, ciò che devo passare è un parametro std :: string e qualcosa che è un valore di ritorno std :: string. Quindi il mio codice è cambiato da qualcosa del genere:

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   api.setup (p1, special_number_to_string(p2));
}

per

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   const std::string p2_storage(special_number_to_string(p2));
   api.setup (string_view(&p1[0], p1.size()), string_view(&p2_storage[0], p2_storage.size()));
}

Io davvero non capisco che cosa questo cambiamento mi ha comprato come il client API, diverso da quello più codice (possibilmente rovinare). La chiamata API è meno sicura (a causa dell'API che non possiede più l'archiviazione per i suoi parametri), probabilmente ha salvato il mio programma 0 di lavoro (a causa di ottimizzazioni di spostamento che i compilatori possono fare ora), e anche se risparmiasse lavoro, sarebbe solo un paio di allocazioni che non verranno e non verrebbero mai eseguite dopo l'avvio o in un grande ciclo da qualche parte. Non per questa API.

Tuttavia, questo approccio sembra seguire i consigli che vedo altrove, ad esempio questa risposta :

A parte questo, dal C ++ 17 dovresti evitare di passare una const std :: string e in favore di una std :: string_view:

Trovo sorprendente questo consiglio, poiché sembra sostenere la sostituzione universale di un oggetto relativamente sicuro con un oggetto meno sicuro (sostanzialmente un puntatore e una lunghezza glorificati), principalmente a fini di ottimizzazione.

Quindi quando usare string_view e quando no?


1
non dovresti mai chiamare std::string_viewdirettamente il costruttore, devi solo passare le stringhe al metodo prendendo std::string_viewdirettamente un metodo e questo si convertirà automaticamente.
Mgetz,

@Mgetz - Hmmm. Non sto (ancora) usando un compilatore C ++ 17 completo, quindi forse è questo il problema. Tuttavia, il codice di esempio qui sembra indicare il suo richiesto, almeno quando lo dichiara.
TED

4
Vedi la mia risposta l'operatore di conversione è <string>nell'intestazione e si verifica automaticamente. Quel codice è ingannevole e sbagliato.
Mgetz,

1
"con una meno sicura" in che modo una fetta è meno sicura di un riferimento di stringa?
CodesInChaos

3
@TED ​​Il chiamante può liberare altrettanto facilmente la stringa a cui fa riferimento il riferimento, così come può liberare la memoria in cui punta lo slice.
CodesInCos

Risposte:


18
  1. La funzionalità che assume il valore deve diventare proprietaria della stringa? In tal caso, utilizzare std::string(non const, non ref). Questa opzione ti dà la possibilità di muoverti esplicitamente anche in un valore se sai che non verrà mai più usato nel contesto chiamante.
  2. La funzionalità legge solo la stringa? In tal caso utilizzare std::string_view(const, non-ref) questo perché string_viewpuò gestire facilmente std::stringe char*senza problemi e senza fare una copia. Questo dovrebbe sostituire tutti i const std::string&parametri.

Alla fine non dovresti mai aver bisogno di chiamare il std::string_viewcostruttore come sei. std::stringha un operatore di conversione che gestisce automaticamente la conversione.


Giusto per chiarire un punto, sto pensando che un tale operatore di conversione si occuperebbe anche dei problemi peggiori della vita, assicurandosi che il valore della stringa RHS rimanga attivo per l'intera durata della chiamata?
TED

3
@TED ​​se stai solo leggendo il valore, il valore sopravvivrà alla chiamata. Se stai assumendo la proprietà, deve superare la chiamata. Ecco perché ho affrontato entrambi i casi. L'operatore di conversione si occupa solo di std::string_viewsemplificare l'utilizzo. Se uno sviluppatore lo usa in modo errato in una situazione proprietaria, si tratta di un errore di programmazione. std::string_viewè strettamente non proprietario.
Mgetz,

Perché const, non-ref? Il parametro essendo const dipende dall'uso specifico, ma in generale è ragionevole come non const. E tu hai perso 3. Può accettare le sezioni
v.

Qual è il problema del passaggio const std::string_view &al posto di const std::string &?
Ceztko,

@ceztko è completamente inutile e aggiunge un'ulteriore direzione indiretta quando si accede ai dati.
Mgetz,

15

A std::string_viewporta alcuni dei vantaggi di a const char*in C ++: a differenza di std::stringstring_view

  • non possiede la memoria,
  • non alloca memoria
  • può puntare a una stringa esistente con un certo offset, e
  • ha un livello di indiretta del puntatore in meno rispetto a std::string&.

Ciò significa che una stringa_vista può spesso evitare copie, senza dover affrontare i puntatori non elaborati.

Nel codice moderno, std::string_viewdovrebbe sostituire quasi tutti gli usi dei const std::string&parametri di funzione. Questa dovrebbe essere una modifica compatibile con la fonte, poiché std::stringdichiara un operatore di conversione a std::string_view.

Solo perché una vista stringa non aiuta nel tuo caso d'uso specifico in cui è comunque necessario creare una stringa non significa che sia una cattiva idea in generale. La libreria standard C ++ tende ad essere ottimizzata per generalità piuttosto che per comodità. L'argomento "meno sicuro" non regge, in quanto non dovrebbe essere necessario creare la vista stringa da soli.


2
Il grande svantaggio di std::string_viewè l'assenza di un c_str()metodo, risultante in std::stringoggetti intermedi non necessari che devono essere costruiti e allocati. Questo è particolarmente un problema nelle API di basso livello.
Matthias,

1
@Matthias Questo è un buon punto, ma non credo sia un grosso svantaggio. Una vista stringa consente di puntare a una stringa esistente con un offset. Quella sottostringa non può essere terminata con zero, per questo è necessaria una copia. Una vista stringa non ti proibisce di fare una copia. Consente molte attività di elaborazione delle stringhe che possono essere eseguite con iteratori. Ma hai ragione nel dire che le API che hanno bisogno di una stringa C non trarranno vantaggio dalle visualizzazioni. Un riferimento di stringa può quindi essere più appropriato.
amon,

@Matthias, string_view :: data () non corrisponde a c_str ()?
Aelian,

3
@Jeevaka una stringa C deve essere terminata con zero, ma i dati di una vista stringa di solito non sono terminati con zero perché indicano una stringa esistente. Ad esempio, se abbiamo una stringa abcdef\0e una vista stringa che punta alla cdesottostringa, non c'è nessun carattere zero dopo il e- la stringa originale ha un flì. Lo standard inoltre nota: “data () può restituire un puntatore a un buffer che non ha terminazione nulla. Pertanto, è in genere un errore passare i dati () a una funzione che accetta solo un carattere const * e si aspetta una stringa con terminazione null. ”
amon

1
@kayleeFrye_onDeck I dati sono già un puntatore a caratteri. Il problema con le stringhe C non sta ottenendo un puntatore a caratteri, ma che una stringa C deve essere terminata con null. Vedi il mio commento precedente per un esempio.
Amon,

8

Trovo sorprendente questo consiglio, poiché sembra sostenere la sostituzione universale di un oggetto relativamente sicuro con un oggetto meno sicuro (sostanzialmente un puntatore e una lunghezza glorificati), principalmente a fini di ottimizzazione.

Penso che questo sia leggermente frainteso lo scopo di questo. Sebbene si tratti di una "ottimizzazione", dovresti davvero pensarlo come sganciato da dover usare a std::string.

Gli utenti di C ++ hanno creato dozzine di diverse classi di stringhe. Classi di stringa a lunghezza fissa, classi ottimizzate SSO con la dimensione del buffer come parametro del modello, classi di stringhe che memorizzano un valore di hash utilizzato per confrontarle, ecc. Alcune persone usano persino stringhe basate su COW. Se c'è una cosa che i programmatori C ++ amano fare, è scrivere classi di stringhe.

E questo ignora le stringhe create e possedute dalle librerie C. Nudo char*, forse con una taglia di qualche tipo.

Quindi, se stai scrivendo una libreria e prendi un const std::string&, ora l'utente deve prendere qualsiasi stringa che stava usando e copiarlo in un std::string. Forse dozzine di volte.

Se si desidera accedere all'interfaccia std::stringspecifica della stringa, perché è necessario copiare la stringa? È un tale spreco.

I motivi principali per non prendere string_viewun parametro come sono:

  1. Se il tuo obiettivo finale è passare la stringa a un'interfaccia che accetta una stringa terminata con NUL ( fopen, ecc.). std::stringè garantito per essere NUL terminato; string_viewnon lo è. Ed è molto semplice eseguire il substring di una vista per renderla non NUL terminata; std::stringla stringa secondaria a copierà la sottostringa in un intervallo terminato con NUL.

    Ho scritto uno speciale tipo di stile string_view terminato con NUL proprio per questo scenario. Puoi eseguire la maggior parte delle operazioni, ma non quelle che interrompono il suo stato terminato con NUL (tagliando dalla fine, per esempio).

  2. Problemi di vita. Se hai davvero bisogno di copiarlo std::stringo altrimenti la matrice di caratteri sopravvive alla chiamata della funzione, è meglio dichiararlo in anticipo prendendo un const std::string &. O semplicemente un std::stringparametro come valore. In questo modo, se hanno già una tale stringa, puoi rivendicarne immediatamente la proprietà e il chiamante può spostarsi nella stringa se non ha bisogno di conservarne una copia.


È vero? L'unica classe di stringa standard di cui ero a conoscenza in C ++ prima era std :: string. C'è un po 'di supporto per usare char * come "stringhe" per la retrocompatibilità con C, ma non ho quasi mai bisogno di usarlo. Certo, ci sono molte classi di terze parti definite dall'utente per quasi tutto ciò che puoi immaginare, e le stringhe sono probabilmente incluse in questo, ma non devo quasi mai usarle.
TED

@TED: Solo perché "non devi quasi mai usarli" non significa che le altre persone non li usino abitualmente. string_viewè un tipo di lingua franca che può funzionare con qualsiasi cosa.
Nicol Bolas,

3
@TED: Ecco perché ho detto "C ++ come ambiente di programmazione", anziché "C ++ come linguaggio / libreria".
Nicol Bolas,

2
@TED: " Quindi potrei anche dire" C ++ in quanto un ambiente di programmazione ha migliaia di classi contenitore "? " E lo fa. Ma posso scrivere algoritmi che funzionano con gli iteratori e qualsiasi classe contenitore che segue quel paradigma funzionerà con loro. Al contrario, gli "algoritmi" che possono richiedere qualsiasi matrice contigua di caratteri erano molto più difficili da scrivere. Con string_view, è facile.
Nicol Bolas

1
@TED: le matrici di caratteri sono un caso molto speciale. Sono estremamente comuni e diversi contenitori di caratteri contigui differiscono solo nel modo in cui gestiscono la memoria, non nel modo in cui si scorre tra i dati. Quindi avere un solo tipo di intervallo di lingua franca in grado di coprire tutti questi casi senza dover utilizzare un modello ha senso. La generalizzazione oltre questa è la provincia della Range TS e dei template.
Nicol Bolas,
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.