Quando devo usar string_view em uma interface?

16

Estou usando uma biblioteca interna que foi projetada para imitar uma biblioteca C ++ proposta e, em algum momento nos últimos anos, vejo sua interface alterada de usar std::stringpara string_view.

Então, eu mudo obedientemente meu código, de acordo com a nova interface. Infelizmente, o que eu tenho que passar é um parâmetro std :: string e algo que é um valor de retorno std :: string. Então, meu código mudou de algo assim:

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

para

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()));
}

Eu realmente não vejo o que esta mudança me comprou como o cliente API, que não seja mais código (possivelmente para estragar). A chamada da API é menos segura (devido ao fato de a API não possuir mais o armazenamento de seus parâmetros), provavelmente salvou o trabalho do meu programa 0 (devido às otimizações de movimentação que os compiladores podem fazer agora) e, mesmo se salvou o trabalho, isso seria apenas algumas alocações que nunca serão e nunca seriam feitas após a inicialização ou em um grande loop em algum lugar. Não para esta API.

No entanto, essa abordagem parece seguir os conselhos que eu vejo em outros lugares, por exemplo, esta resposta :

Como um aparte, desde o C ++ 17, você deve evitar passar uma const std :: string e a favor de uma std :: string_view:

Acho esse conselho surpreendente, pois parece defender universalmente a substituição de um objeto relativamente seguro por um objeto menos seguro (basicamente um ponteiro e comprimento glorificados), principalmente para fins de otimização.

Então, quando string_view deve ser usado, e quando não deveria?

TED
fonte
1
você nunca deve chamar o std::string_viewconstrutor diretamente, basta passar as strings para o método que recebe um std::string_viewdiretamente e ele será convertido automaticamente.
Mgetz
@Mgetz - Hmmm. Ainda não estou usando um compilador C ++ 17 completo, então talvez isso seja a maior parte do problema. Ainda assim, o código de amostra aqui parecia indicar seu requisito, pelo menos ao declarar um.
TED
4
Veja minha resposta: o operador de conversão está no <string>cabeçalho e acontece automaticamente. Esse código é enganador e errado.
Mgetz
1
"com um menos seguro", como uma fatia é menos segura do que uma referência de string?
CodesInChaos 16/01
3
@TED ​​O chamador pode facilmente liberar a string que sua referência está apontando, assim como pode liberar a memória na qual a fatia está apontando.
CodesInChaos

Respostas:

18
  1. A funcionalidade que obtém o valor precisa se apropriar da sequência? Se sim, use std::string(non-const, non-ref). Essa opção oferece a opção de mover explicitamente um valor também, se você souber que ele nunca será usado novamente no contexto de chamada.
  2. A funcionalidade acabou de ler a string? std::string_viewNesse caso, use (const, non-ref), porque isso string_viewpode ser feito facilmente std::stringe char*sem problemas e sem fazer uma cópia. Isso deve substituir todos os const std::string&parâmetros.

Em última análise, você nunca precisará chamar o std::string_viewconstrutor como você é. std::stringpossui um operador de conversão que lida com a conversão automaticamente.

Mgetz
fonte
Apenas para esclarecer um ponto, acho que esse operador de conversão também resolveria os piores problemas da vida útil, garantindo que o valor da string RHS permaneça em torno de toda a duração da chamada?
TED
3
@TED ​​se você está apenas lendo o valor, ele superará a chamada. Se você está assumindo a propriedade, ele precisa durar mais que a chamada. Daí porque eu lidei com os dois casos. O operador de conversão trata apenas de std::string_viewfacilitar a utilização. Se um desenvolvedor o usa mal em uma situação de propriedade, isso é um erro de programação. std::string_viewé estritamente não proprietário.
Mgetz
Por que const, non-ref? O parâmetro sendo const é de uso específico, mas em geral é razoável como não-const. E você perdeu 3. Pode aceitar fatias
v.oddou 13/05/19
Qual é o problema de passar const std::string_view &no lugar de const std::string &?
Ceztko
@ceztko é completamente desnecessário e adiciona um indireto extra ao acessar os dados.
Mgetz
15

A std::string_viewtraz alguns dos benefícios de a const char*para C ++: ao contrário std::string, um string_view

  • não possui memória,
  • não aloca memória,
  • pode apontar para uma cadeia existente em algum deslocamento e
  • tem um nível a menos de indireção de ponteiro que a std::string&.

Isso significa que um string_view geralmente pode evitar cópias, sem precisar lidar com ponteiros brutos.

No código moderno, std::string_viewdeve substituir quase todos os usos de const std::string&parâmetros de função. Essa alteração deve ser compatível com a fonte, pois std::stringdeclara um operador de conversão como std::string_view.

Só porque uma exibição de sequência não ajuda em seu caso de uso específico, em que você precisa criar uma sequência de qualquer maneira, não significa que seja uma má ideia em geral. A biblioteca padrão C ++ tende a ser otimizada por generalidade e não por conveniência. O argumento "menos seguro" não é válido, pois não deve ser necessário criar você mesmo a visualização de string.

amon
fonte
2
A grande desvantagem de std::string_viewé a ausência de um c_str()método, resultando em std::stringobjetos intermediários desnecessários que precisam ser construídos e alocados. Isso é especialmente um problema nas APIs de baixo nível.
Matthias
1
@ Matthias Esse é um bom ponto, mas não acho que seja uma grande desvantagem. Uma exibição de sequência permite apontar para uma sequência existente em algum deslocamento. Essa substring não pode ser terminada com zero, você precisa de uma cópia para isso. Uma exibição de seqüência de caracteres não o proíbe de fazer uma cópia. Ele permite muitas tarefas de processamento de cadeia que podem ser executadas com iteradores. Mas você está certo de que as APIs que precisam de uma cadeia C não lucram com as visualizações. Uma referência de string pode ser mais apropriada.
amon
@ Matthias, string_view :: data () corresponde a c_str ()?
Aelian
3
@ Jeevaka, uma string C deve ser terminada com zero, mas os dados de uma exibição de string geralmente não são terminados com zero porque apontam para uma string existente. Por exemplo, se tivermos uma string abcdef\0e uma exibição de string que aponte para a cdesubstring, não haverá um caractere zero após o e- a string original possui um f. O padrão também observa: “data () pode retornar um ponteiro para um buffer que não possui terminação nula. Por isso, é normalmente um erro para passar dados () para uma função que leva apenas um gráfico const * e espera uma string terminada em nulo “.
amon
1
@kayleeFrye_onDeck Os dados já são um ponteiro de char. O problema com as seqüências de caracteres C não é obter um ponteiro de caractere, mas que uma sequência de caracteres C deve ter terminação nula. Veja meu comentário anterior para um exemplo.
amon
8

Acho esse conselho surpreendente, pois parece defender universalmente a substituição de um objeto relativamente seguro por um objeto menos seguro (basicamente um ponteiro e comprimento glorificados), principalmente para fins de otimização.

Eu acho que isso é um pouco mal entendido do objetivo disso. Embora seja uma "otimização", você deve realmente pensar nisso como se livrar de ter que usar umstd::string .

Usuários de C ++ criaram dezenas de diferentes classes de strings. Classes de string de comprimento fixo, classes otimizadas para SSO com o tamanho do buffer como parâmetro do modelo, classes de string que armazenam um valor de hash usado para compará-las, etc. Algumas pessoas até usam strings baseadas em COW. Se há uma coisa que os programadores de C ++ gostam de fazer, é escrever classes de string.

E isso ignora as strings criadas e pertencentes às bibliotecas C. N nu char*, talvez com algum tamanho.

Portanto, se você estiver escrevendo alguma biblioteca e fizer uma const std::string&, agora o usuário precisará pegar qualquer string que estiver usando e copiá-la para a std::string. Talvez dezenas de vezes.

Se você deseja acessar a std::stringinterface específica de cadeia de caracteres, por que você deve copiar a string? Isso é um desperdício.

Os principais motivos para não usar a string_viewcomo parâmetro são:

  1. Se seu objetivo final é passar a string para uma interface que use uma string terminada em NUL ( fopen, etc). std::stringé garantido para ser NUL terminado; string_viewnão é. E é muito fácil criar uma exibição de substring para torná-la sem terminação NUL; sub-string a std::stringcopiará a subcadeia em um intervalo terminado por NUL.

    Eu escrevi um tipo especial de estilo string_view terminado em NUL para exatamente esse cenário. Você pode executar a maioria das operações, mas não as que quebram seu status de NUL (aparando a partir do final, por exemplo).

  2. Problemas ao longo da vida. Se você realmente precisa copiar isso std::stringou, caso contrário, a matriz de caracteres sobreviver à chamada de função, é melhor declarar isso com antecedência usando a const std::string &. Ou apenas um std::stringparâmetro como value. Dessa forma, se eles já tiverem essa sequência, você poderá reivindicar a propriedade imediatamente, e o chamador poderá passar para a sequência se não precisar manter uma cópia dela.

Nicol Bolas
fonte
Isso é verdade? A única classe de string padrão que eu conhecia no C ++ antes disso era std :: string. Há algum suporte para usar char * 's como "strings" para compatibilidade com versões anteriores com C, mas quase nunca preciso usá-lo. Claro, existem muitas classes de terceiros definidas pelo usuário para quase qualquer coisa que você possa imaginar, e as strings provavelmente estão incluídas nisso, mas eu quase nunca preciso usá-las.
TED
@TED: Só porque você "quase nunca precisa usá-las" não significa que outras pessoas não as usem rotineiramente. string_viewé um tipo de lingua franca que pode funcionar com qualquer coisa.
Nicol Bolas
3
@TED: É por isso que eu disse "C ++ como ambiente de programação", em oposição a "C ++ como linguagem / biblioteca".
Nicol Bolas
2
@TED: " Então, eu poderia dizer igualmente" C ++ como um ambiente de programação tem milhares de classes de contêineres "? " Mas eu posso escrever algoritmos que funcionam com iteradores, e qualquer classe de contêiner que segue esse paradigma funcionará com eles. Por outro lado, "algoritmos" que podem ter qualquer matriz contígua de caracteres era muito mais difícil de escrever. Com string_view, é fácil.
Nicol Bolas
1
@TED: matrizes de caracteres são um caso muito especial. Eles são extremamente comuns, e diferentes contêineres de caracteres contíguos diferem apenas na maneira como gerenciam sua memória, não na maneira como você itera nos dados. Portanto, ter um único tipo de intervalo de língua franca que possa cobrir todos esses casos sem precisar empregar um modelo faz sentido. A generalização além disso é a província do intervalo TS e modelos.
Nicol Bolas