C++ string_view C++17
Uma std::string é dona do seu texto: ela guarda as letras na memória e, quando você a copia, todas as letras são copiadas. Muitas vezes, porém, você só quer ler um texto que já existe, ou um pedaço dele, sem fazer cópia nenhuma. Para isso existe o std::string_view: uma "janela" para um texto que pertence a outra pessoa. É leve e rápido, mas tem um perigo sério, que esta aula mostra com calma: se o dono do texto sumir, a janela passa a mostrar lixo.
- O que é um
std::string_viewe o que ele guarda (e o que não guarda). - Usar
std::string_viewcomo parâmetro de função, aceitandostd::stringe literais. - Cortar pedaços sem copiar:
substr,remove_prefix,remove_suffix. - Voltar de
string_viewparastd::stringquando precisar. - O perigo de apontar para uma string temporária: o que acontece, por que o g++ não avisa e quem avisa.
Os programas foram compilados com o g++ 15.2 na máquina fictícia da Ana, na pasta ~/cpp, com g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa. Na seção do perigo aparece também o clang++ 21, o mesmo compilador do editor do guia. Antes desta aula, vale ter visto Strings, Funções de String e Por Valor × Por Referência.
A ideia: o livro e o marcador de página
A Ana tem um livro (a std::string). Ela quer mostrar a uma amiga um parágrafo da página 40. Ela pode fotocopiar o parágrafo (fazer uma cópia, com custo) ou simplesmente apontar: "olha aqui, da linha 3 até a linha 8". Apontar é de graça. Mas, se alguém levar o livro embora, o dedo da Ana aponta para o nada.
O string_view é o dedo apontando. Por dentro, ele guarda só duas coisas:
| Guarda | Na analogia |
|---|---|
| Onde o texto começa na memória (um ponteiro) | "A partir desta linha" |
| Quantos caracteres olhar | "…estas 6 linhas" |
Ele não guarda as letras. Por isso copiar um string_view é barato, não importa o tamanho do texto, e por isso ele depende de o texto original continuar existindo.
1. Criar e ler um string_view
#include <iostream>
#include <string>
#include <string_view>
int main() {
std::string frase = "Lista de compras da Ana";
std::string_view visao = frase;
std::cout << visao << '\n';
std::cout << "Tamanho: " << visao.size() << '\n';
std::cout << "Primeira letra: " << visao[0] << '\n';
std::cout << "Começa com \"Lista\"? " << std::boolalpha << visao.starts_with("Lista") << '\n';
std::string_view literal = "Olá, mundo";
std::cout << literal << " (" << literal.size() << " bytes)\n";
}
$ g++ -std=c++23 -Wall -Wextra basico.cpp -o basico $ ./basico Lista de compras da Ana Tamanho: 23 Primeira letra: L Começa com "Lista"? true Olá, mundo (11 bytes) « 10 letras, mas o "á" ocupa 2 bytes
- #include <string_view>
- O cabeçalho do
std::string_view(view = "vista", "visão"). - std::string_view visao = frase;
- A janela passa a olhar para o texto de
frase. Nenhuma letra foi copiada. - visao.size(), visao[0]
- Os mesmos nomes da
std::string: tamanho e acesso a um caractere. - visao.starts_with("Lista")
- "Começa com…?" (C++20; também existe
ends_with, "termina com"). - std::string_view literal = "Olá, mundo";
- Também dá para olhar direto para um literal. Literais vivem durante o programa inteiro, então essa janela é sempre segura.
Um string_view é só para ler: visao[0] = 'X'; não compila. Ele tem quase todos os métodos de leitura da string (find, rfind, substr, compare, ==…), mas nenhum que mude o texto (+=, append, insert).
A janela vê as mudanças do dono
Como o string_view não tem cópia, se o texto original mudar, a janela mostra a mudança:
#include <iostream>
#include <string>
#include <string_view>
int main() {
std::string frase = "bom dia";
std::string_view visao = frase;
frase[0] = 'B';
std::cout << visao << '\n';
std::string copia = frase.substr(0, 3);
std::string_view pedaco = visao.substr(0, 3);
frase[1] = 'O';
std::cout << "copia: " << copia << '\n';
std::cout << "pedaco: " << pedaco << '\n';
}
$ g++ -std=c++23 -Wall -Wextra mesma_memoria.cpp -o mesma_memoria $ ./mesma_memoria Bom dia « a view viu o 'B' trocado na string copia: Bom « a cópia ficou como estava pedaco: BOm « a view do pedaço viu o 'O'
Esse exemplo mostra a diferença essencial: frase.substr(0, 3) de uma string cria uma string nova (copia 3 letras); visao.substr(0, 3) de uma view cria outra view (copia só o "onde" e o "quantos").
2. O uso principal: parâmetro de função
Onde o string_view mais aparece é no parâmetro de funções que só leem um texto:
#include <iostream>
#include <string>
#include <string_view>
void saudar(std::string_view nome) {
std::cout << "Olá, " << nome << "!\n";
}
int main() {
std::string ana = "Ana";
saudar(ana); // uma std::string
saudar("Bruno"); // um literal
std::string_view c = "Carla Souza";
saudar(c.substr(0, 5)); // um pedaço de outra view
}
$ g++ -std=c++23 -Wall -Wextra funcao.cpp -o funcao $ ./funcao Olá, Ana! Olá, Bruno! Olá, Carla!
Por que não const std::string&, que você aprendeu em Por Valor × Por Referência? Compare as três formas:
| Parâmetro | Chamada com std::string | Chamada com "Bruno" (literal) | Chamada com um pedaço |
|---|---|---|---|
std::string nome | Copia o texto | Cria uma string nova | Cria uma string nova |
const std::string& nome | Sem cópia | Cria uma string temporária só para poder fazer a referência | Precisa de s.substr(…), que cria uma string nova |
std::string_view nome | Sem cópia | Sem cópia | Sem cópia |
Com string_view, a função aceita qualquer tipo de texto e nunca copia. A diferença só aparece em programas que chamam a função milhões de vezes; mesmo assim, virou o costume moderno para parâmetros de texto "só de leitura". Repare que o string_view é passado por valor (sem &): ele é tão pequeno (um ponteiro e um tamanho) que copiá-lo é tão barato quanto passar uma referência.
Se a função vai guardar o texto para depois (num campo de uma classe, num vetor), receba std::string: quem guarda precisa ser dono. Guardar um string_view é pedir para ele ficar apontando para um texto que pode sumir.
Experimente: a função conta vogais e aceita tanto std::string quanto literais. Rode. Depois chame contar_vogais com um pedaço da frase, sem copiar: std::string_view(frase).substr(0, 9) (só "programar").
#include <iostream>
#include <string>
#include <string_view>
int contar_vogais(std::string_view texto) {
int total = 0;
for (char c : texto) {
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
++total;
}
}
return total;
}
int main() {
std::string frase = "programar em casa";
std::cout << contar_vogais(frase) << '\n';
std::cout << contar_vogais("banana") << '\n';
}
3. Cortar pedaços de graça
Para "picar" um texto (separar campos de uma linha, tirar uma extensão de arquivo), o string_view brilha: cada pedaço é só uma janela nova sobre o mesmo texto.
#include <iostream>
#include <string_view>
int main() {
std::string_view linha = "2026-10-16;Ana;arroz;12.50";
std::string_view data = linha.substr(0, 10);
std::string_view resto = linha.substr(11);
std::cout << "Data: " << data << '\n';
std::cout << "Resto: " << resto << '\n';
auto pos = resto.find(';');
std::string_view nome = resto.substr(0, pos);
std::cout << "Nome: " << nome << '\n';
std::string_view arquivo = "relatorio.txt";
arquivo.remove_suffix(4);
std::cout << "Sem extensão: " << arquivo << '\n';
arquivo.remove_prefix(3);
std::cout << "Sem 3 primeiras: " << arquivo << '\n';
}
$ g++ -std=c++23 -Wall -Wextra pedacos.cpp -o pedacos $ ./pedacos Data: 2026-10-16 Resto: Ana;arroz;12.50 Nome: Ana Sem extensão: relatorio Sem 3 primeiras: atorio
- linha.substr(0, 10)
- Da posição 0, 10 caracteres. Igual ao
substrda string, mas devolve uma view. - linha.substr(11)
- Da posição 11 até o fim.
- resto.find(';')
- A posição do primeiro
;(oustd::string_view::npos, "posição nenhuma", se não houver). - arquivo.remove_suffix(4)
- Encolhe a janela, tirando 4 caracteres do fim. O texto original não muda; só a janela.
- arquivo.remove_prefix(3)
- Encolhe a janela, tirando 3 caracteres do começo.
4. De volta para std::string
Quando você precisar de uma string de verdade (para mudar o texto, juntar com +=, ou guardar), crie uma a partir da view. Mas não com =:
$ cat para_string.cpp
#include <string>
#include <string_view>
int main() {
std::string_view sv = "Ana";
std::string s = sv;
return static_cast<int>(s.size());
}
$ g++ -std=c++23 -Wall -Wextra para_string.cpp -o para_string
para_string.cpp: In function ‘int main()’:
para_string.cpp:6:21: error: conversion from ‘std::string_view’ {aka ‘std::basic_string_view<char>’} to non-scalar type ‘std::string’ {aka ‘std::__cxx11::basic_string<char>’} requested
6 | std::string s = sv;
| ^~
O C++ não deixa a conversão acontecer "escondida" com =, porque ela custa uma cópia, e o objetivo do string_view é justamente evitar cópias sem você perceber. Você tem de pedir explicitamente:
#include <iostream>
#include <string>
#include <string_view>
int main() {
std::string_view sv = "Ana";
std::string s{sv};
std::string t = std::string(sv);
s += " Maria";
std::cout << s << " / " << t << '\n';
}
$ g++ -std=c++23 -Wall -Wextra para_string_ok.cpp -o para_string_ok $ ./para_string_ok Ana Maria / Ana
O caminho contrário é automático: toda std::string vira string_view sem você escrever nada (por isso saudar(ana) funcionou).
5. O perigo: apontar para um texto que já morreu
Agora o lado ruim. Esta função devolve uma std::string nova. O main guarda o resultado numa string_view:
#include <iostream>
#include <string>
#include <string_view>
std::string nome_completo() {
return "Ana Maria da Silva Souza";
}
int main() {
std::string_view nome = nome_completo();
std::cout << nome << '\n';
}
O que acontece na linha 10, passo a passo:
nome_completo()cria umastd::stringtemporária, sem nome, com o texto.nomepassa a apontar para as letras dessa temporária.- No
;, a linha acaba, e a temporária é destruída: a memória das letras é devolvida. - Na linha 11,
nomeaponta para uma memória que não pertence mais a ninguém. Ler dali é comportamento indefinido.
E o g++? Não diz nada:
$ g++ -std=c++23 -Wall -Wextra pendurada.cpp -o pendurada « nenhum aviso $ ./pendurada | cat -v '?^MM-'^E^@^@^@M-^HEgM-z1M-^[)^Fva Souza $ ./pendurada | cat -v ~I^NM-^^E^@^@^@#I^[^Z^Tc=M-,va Souza « lixo diferente a cada execução
O | cat -v só serve para mostrar os bytes "invisíveis" do lixo como ^E, M- etc., em vez de bagunçar o terminal. O começo do nome foi sobrescrito por outros dados quando a memória foi devolvida; o fim (va Souza) ainda estava lá, por acaso. Num programa maior, isso pode virar dados errados, travamentos ou falhas de segurança, e o pior: às vezes "funciona", o que esconde o erro.
Testamos também com -O2, com -Wdangling-reference e com -Wdangling-pointer=2: o g++ 15 continuou calado nesse caso. Quem avisa é o Clang, o compilador do editor do guia:
$ clang++ -std=c++23 -Wall -Wextra pendurada.cpp -o pendurada_clang
pendurada.cpp:10:29: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl]
10 | std::string_view nome = nome_completo();
| ^~~~~~~~~~~~~~~
1 warning generated.
- object backing the pointer
- "O objeto por trás do ponteiro": a string temporária que guarda as letras.
- will be destroyed at the end of the full-expression
- "Será destruído no fim da expressão completa", ou seja, no
;desta linha. - [-Wdangling-gsl]
- O nome do aviso. Dangling = "pendurado", "solto": um ponteiro (ou view) que aponta para algo que não existe mais.
O mesmo erro aparece quando uma função devolve uma view de uma variável local:
std::string_view saudacao(const std::string& nome) {
std::string texto = "Bom dia, " + nome + "! Tudo bem com você?";
return texto; // texto morre aqui, no fim da função
}
$ g++ -std=c++23 -Wall -Wextra pendurada2.cpp -o pendurada2 « de novo, nenhum aviso
$ clang++ -std=c++23 -Wall -Wextra pendurada2.cpp -o pendurada2_clang
pendurada2.cpp:7:12: warning: address of stack memory associated with local variable 'texto' returned [-Wreturn-stack-address]
7 | return texto;
| ^~~~~
1 warning generated.
Address of stack memory associated with local variable 'texto' returned = "foi devolvido o endereço de memória da pilha ligada à variável local texto". A variável local morre quando a função termina (aula Escopo).
Pegando o erro com o AddressSanitizer
Como o g++ não avisa na compilação, a ferramenta para pegar esse erro ao rodar é o AddressSanitizer (aula Sanitizers). Ele aponta a linha da leitura (11) e a linha em que a memória foi liberada (10). A saída completa tem dezenas de linhas; aqui, filtrada com grep:
$ g++ -std=c++23 -Wall -Wextra -g -fsanitize=address pendurada.cpp -o pendurada_asan
$ ./pendurada_asan 2>&1 | grep -E "ERROR|READ|#3 .*main|freed by|#7 .*main|SUMMARY"
==543719==ERROR: AddressSanitizer: heap-use-after-free on address 0x74923afe0040 at pc 0x78623c48aae8 bp 0x7ffe3730e3d0 sp 0x7ffe3730db88
READ of size 24 at 0x74923afe0040 thread T0
#3 0x5d899726c6e0 in main /home/ana/cpp/pendurada.cpp:11
freed by thread T0 here:
#7 0x5d899726c689 in main /home/ana/cpp/pendurada.cpp:10
SUMMARY: AddressSanitizer: heap-use-after-free (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0x162d9b) (BuildId: 3a7a8c6bd922e6314edb7a710fdc0293265c2c42) in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
Heap-use-after-free = "uso da memória depois de liberada". READ of size 24: o programa tentou ler os 24 bytes do nome. Os números em hexadecimal mudam a cada execução.
string_view
- Use-o para parâmetros de funções que só leem: aí o texto de quem chamou com certeza existe durante a chamada.
- Nunca guarde numa
string_viewo resultado de uma função que devolvestd::string. Guarde numastd::string. - Nunca devolva uma
string_viewque aponta para uma variável local. - Devolver uma view é seguro quando ela aponta para algo que vive mais que a função: um literal ou o próprio parâmetro recebido (como na missão final).
- Na dúvida, use
std::string. Uma cópia a mais quase nunca é problema; um ponteiro solto sempre é.
Missão: rode este programa no editor. O Clang do editor mostra o aviso -Wdangling-gsl e a saída pode sair estragada. Conserte trocando o tipo da variável nome, para que ela seja dona do texto. A meta é compilar sem aviso e mostrar o nome inteiro.
#include <iostream>
#include <string>
#include <string_view>
std::string nome_completo() {
return "Ana Maria da Silva Souza";
}
int main() {
std::string_view nome = nome_completo();
std::cout << nome << '\n';
}
#include <iostream>
#include <string>
#include <string_view>
std::string nome_completo() {
return "Ana Maria da Silva Souza";
}
int main() {
std::string nome = nome_completo();
std::cout << nome << '\n';
}
std::string ou std::string_view? Qual usar
| Situação | Use |
|---|---|
| Parâmetro de uma função que só lê o texto | std::string_view |
| Variável local que guarda o resultado de uma função | std::string |
| Campo de uma struct/classe | std::string |
Você vai mudar o texto (+=, insert, trocar letras) | std::string |
| Picar uma linha em pedaços que você só vai ler logo em seguida | std::string_view |
Passar o texto para uma função do C que espera const char* terminado em '\0' | std::string e .c_str(): uma view de um pedaço não tem o '\0' no fim |
Quando dá errado
O que significa: uma string_view está olhando para uma std::string que já foi destruída: uma temporária (resultado de função, s1 + s2) ou uma variável local que saiu de escopo.
Como consertar: guarde o texto numa std::string. Para encontrar o erro, compile também com clang++ -Wall (aviso -Wdangling-gsl ou -Wreturn-stack-address) ou rode com -fsanitize=address (heap-use-after-free).
error: conversion from ‘std::string_view’ … to non-scalar type ‘std::string’ … requested
O que significa: você tentou std::string s = sv;. A conversão de view para string não acontece sozinha.
Como consertar: std::string s{sv}; ou std::string s = std::string(sv);.
error: ‘string_view’ is not a member of ‘std’
$ g++ -std=c++14 -Wall -Wextra basico.cpp -o b14
basico.cpp: In function ‘int main()’:
basico.cpp:7:10: error: ‘string_view’ is not a member of ‘std’
7 | std::string_view visao = frase;
| ^~~~~~~~~~~
basico.cpp:7:10: note: ‘std::string_view’ is only available from C++17 onwards
« … e outros erros que vêm deste
O que significa: o programa foi compilado com um padrão anterior ao C++17, e a nota diz isso com todas as letras: "só está disponível a partir do C++17". Também pode ser falta do #include <string_view>. (No g++ 15, um programa sem esse include mas com <iostream> compilou, porque o <iostream> o trouxe "de carona"; não conte com isso em outros compiladores.)
Como consertar: compile com -std=c++23 (ou pelo menos -std=c++17) e inclua o cabeçalho. starts_with e ends_with exigem C++20.
Referência rápida
| Escrita | O que faz |
|---|---|
std::string_view v = s; | Janela para a string s (ou um literal), sem cópia |
v.size(), v.empty(), v[i] | Tamanho, vazio?, caractere (só leitura) |
v.substr(pos, n) | Outra view: um pedaço, sem cópia |
v.find(x), v.rfind(x) | Posição da primeira / última ocorrência, ou std::string_view::npos |
v.remove_prefix(n) / v.remove_suffix(n) | Encolhe a janela no começo / no fim |
v.starts_with(x) / v.ends_with(x) | Começa / termina com? (C++20) |
std::string s{v}; | Cópia de verdade, quando você precisar ser dono |
void f(std::string_view texto) | Parâmetro que aceita string, literal e pedaços, sem cópia |
Exercícios
1. O cabeçalho
Complete para poder usar std::string_view:
2. Virar dono
Complete para criar uma std::string a partir da view v:
3. O que um std::string_view guarda por dentro?
4. Qual destas linhas cria uma view pendurada (que aponta para texto destruído)?
5. Uma classe Contato precisa guardar o nome da pessoa num campo. Qual tipo usar para o campo?
Missão final: a extensão do arquivo
Missão: complete extensao: ache a posição do último ponto com rfind('.'). Se não houver ponto (std::string_view::npos), devolva "(sem extensão)"; senão, devolva o pedaço depois do ponto com substr(ponto + 1). A saída deve ser:
txt png (sem extensão)
#include <iostream>
#include <string>
#include <string_view>
std::string_view extensao(std::string_view arquivo) {
// sua vez
return arquivo;
}
int main() {
std::string foto = "ferias.praia.png";
std::cout << extensao("notas.txt") << '\n';
std::cout << extensao(foto) << '\n';
std::cout << extensao("LEIAME") << '\n';
}
#include <iostream>
#include <string>
#include <string_view>
std::string_view extensao(std::string_view arquivo) {
auto ponto = arquivo.rfind('.');
if (ponto == std::string_view::npos) {
return "(sem extensão)";
}
return arquivo.substr(ponto + 1);
}
int main() {
std::string foto = "ferias.praia.png";
std::cout << extensao("notas.txt") << '\n';
std::cout << extensao(foto) << '\n';
std::cout << extensao("LEIAME") << '\n';
}
Aqui devolver uma view é seguro: ela aponta para dentro do texto que quem chamou passou (a foto do main, ou um literal), e esse texto continua vivo depois que extensao termina. A solução no laboratório:
$ g++ -std=c++23 -Wall -Wextra extensao.cpp -o extensao $ ./extensao txt png (sem extensão)
Resumo
std::string_view(<string_view>) é uma janela de leitura: guarda onde o texto começa e quantos caracteres olhar, sem copiar.- O uso principal é o parâmetro de funções que só leem: aceita
std::string, literais e pedaços, sem cópia. Passe por valor. substr,remove_prefixeremove_suffixde uma view são baratos: só movem a janela.- Para virar dono do texto:
std::string s{v};. - A view não mantém o texto vivo. Apontar para uma string temporária ou local é comportamento indefinido; o g++ 15 não avisa, o Clang avisa (
-Wdangling-gsl,-Wreturn-stack-address) e o AddressSanitizer pega ao rodar. - Variáveis que guardam resultados e campos de classes:
std::string.