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.

Você vai aprender
  • O que é um std::string_view e o que ele guarda (e o que não guarda).
  • Usar std::string_view como parâmetro de função, aceitando std::string e literais.
  • Cortar pedaços sem copiar: substr, remove_prefix, remove_suffix.
  • Voltar de string_view para std::string quando 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:

GuardaNa 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âmetroChamada com std::stringChamada com "Bruno" (literal)Chamada com um pedaço
std::string nomeCopia o textoCria uma string novaCria uma string nova
const std::string& nomeSem cópiaCria uma string temporária só para poder fazer a referênciaPrecisa de s.substr(…), que cria uma string nova
std::string_view nomeSem cópiaSem cópiaSem 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.

Quando a função precisa guardar o texto

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 substr da string, mas devolve uma view.
linha.substr(11)
Da posição 11 até o fim.
resto.find(';')
A posição do primeiro ; (ou std::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:

  1. nome_completo() cria uma std::string temporária, sem nome, com o texto.
  2. nome passa a apontar para as letras dessa temporária.
  3. No ;, a linha acaba, e a temporária é destruída: a memória das letras é devolvida.
  4. Na linha 11, nome aponta 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.

As regras de ouro do 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_view o resultado de uma função que devolve std::string. Guarde numa std::string.
  • Nunca devolva uma string_view que 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çãoUse
Parâmetro de uma função que só lê o textostd::string_view
Variável local que guarda o resultado de uma funçãostd::string
Campo de uma struct/classestd::string
Você vai mudar o texto (+=, insert, trocar letras)std::string
Picar uma linha em pedaços que você só vai ler logo em seguidastd::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

Lixo no lugar do texto (e nenhum aviso do g++)

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

EscritaO 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:

#include <>

2. Virar dono

Complete para criar uma std::string a partir da view v:

std::string s;

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_prefix e remove_suffix de 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.