Extensão — C++ no Mundo da Web e da Persistência

Extensão — C++ no Mundo da Web e da Persistência

Usar C++ para servir web é escolha de requisito, não de gosto: ganha onde desempenho e controle de memória pesam mais que velocidade de desenvolvimento. O panorama cobre o que existe de framework HTTP e conector de banco, a diferença entre SQLite e PostgreSQL, e o que muda ao sair da memória para o disco.
Linguagem C++

12 min de leitura

A série principal terminou no artigo A Jornada Completa — Retrospectiva de Um Ano de C++ com a retrospectiva da jornada. Mas lá no comecinho do curso você levantou uma pergunta que guardamos deliberadamente para o fim: e C++ na web, com bancos de dados de verdade — SQLite, PostgreSQL, MySQL, MongoDB —, templates, renderização? Prometi que, depois do capstone, abriríamos uma trilha-extensão sobre isso, uma décima fase bônus. Chegou a hora, e ela é a ponte perfeita entre o que você dominou e um domínio de aplicação concreto. Nosso mini banco em memória do capstone tem um parente natural: os bancos reais em disco. E aquele std::cout que imprime no terminal tem um parente também: a resposta HTTP que um servidor envia a um navegador. Este artigo é o panorama honesto — o que C++ faz bem nesse território, o que não faz, e o mapa das ferramentas — antes de sujarmos as mãos com código nos próximos artigos.

A pergunta honesta: C++ é uma linguagem "de web"?

Comecemos pela franqueza, porque ela orienta tudo. C++ não é uma linguagem "de web" no sentido em que Python, JavaScript ou PHP são. Para construir um site típico rapidamente, com muita lógica de apresentação e pouca exigência de desempenho, aquelas linguagens são mais produtivas — têm frameworks maduros, ecossistemas vastos, e menos cerimônia. Vender C++ como escolha padrão para web seria desonesto. Então por que usar C++ aqui? Por uma razão específica e poderosa: desempenho e controle. Quando um serviço precisa processar um volume imenso de requisições com latência mínima, quando cada milissegundo e cada byte de memória importam, quando o backend faz trabalho computacionalmente pesado — aí o C++ brilha. É por isso que peças críticas de infraestrutura (servidores de alta performance, bancos de dados, motores de jogos com componente online, sistemas de trading) são escritas em C++. A regra: use C++ para web quando o desempenho for o requisito dominante; caso contrário, considere ferramentas mais ágeis. Esta extensão é sobre o cenário em que C++ é a escolha certa, não a conveniente.

O território da persistência: do memória ao disco

Nosso banco do capstone guardava tudo na RAM — rápido, mas volátil: desligou, perdeu. Bancos reais persistem em disco e sobrevivem ao desligamento. C++ conversa com todos os principais, através de bibliotecas conectoras. Vale o mapa, com uma linha de contexto para cada, porque conhecer o terreno é o primeiro passo:

O SQLite é um banco relacional embutido — não um servidor separado, mas uma biblioteca que roda dentro do seu programa e guarda tudo num único arquivo. É o mais simples de integrar (é literalmente uma biblioteca C que você inclui), perfeito para aplicações locais, e será nosso foco no próximo artigo justamente por essa simplicidade e por ser onipresente (está em quase todo smartphone e navegador). O PostgreSQL é um banco relacional servidor, robusto e cheio de recursos, acessado em C++ tipicamente via a biblioteca libpqxx — a escolha comum para aplicações sérias que precisam de um banco relacional completo. O MySQL (e seu fork MariaDB) é outro banco relacional servidor popular, com o conector oficial MySQL Connector/C++. E o MongoDB é um banco de documentos (não relacional, guarda JSON-like em vez de tabelas), acessado via o driver mongocxx — útil quando seus dados não se encaixam bem no modelo de tabelas rígidas.

A boa notícia é que os conceitos que você domina se transferem diretamente. Um conector de banco é um recurso — abre uma conexão, você a usa, precisa fechá-la. Isso é RAII (Fase 2): você envolve a conexão num objeto que a abre no construtor e a fecha no destrutor, e nunca vaza uma conexão. Os resultados de uma consulta são coleções de registros — a STL (Fase 4) os armazena. Erros de conexão ou consulta são falhasexpected ou exceções (Fase 6) os modelam. O banco real é o nosso capstone, escalado: os mesmos princípios, aplicados a dados que persistem.

O território da web: servidores e templates

Do lado da web, o mapa também é navegável. Para fazer C++ responder a requisições HTTP, há frameworks de servidor: o Crow e o Drogon (frameworks completos, com roteamento e mais), o Pistache, e o leve cpp-httplib (uma biblioteca de cabeçalho único, simples de integrar — que usaremos no próximo artigo justamente por isso). Eles recebem requisições, deixam você escrever a lógica de resposta, e enviam o resultado — conceitualmente, um std::cout que fala HTTP em vez de terminal. Para gerar HTML dinâmico (páginas montadas com dados), há motores de template como o inja (inspirado no Jinja do Python) e implementações de Mustache, que preenchem moldes HTML com seus dados — o equivalente a um printf sofisticado para páginas web.

E de novo, seus conceitos se transferem. Um servidor HTTP frequentemente lida com muitas requisições simultâneas — concorrência (Fase 7), com threads atendendo requisições em paralelo, e toda a disciplina de sincronização que você aprendeu. As rotas e handlers são organizados em classes (Fase 3). Os dados que fluem são modelados com os tipos do C++ moderno (Fase 6). Um servidor web em C++ é uma aplicação concorrente que usa tudo do curso.

A honestidade sobre esta extensão

Prometo a franqueza de sempre, especialmente aqui. Esta trilha-extensão é um panorama e uma introdução, não um curso completo de desenvolvimento web ou de bancos de dados em C++ — cada um desses tópicos renderia uma série própria. Meu objetivo é mostrar que o C++ que você dominou se aplica a esses domínios, dar-lhe o vocabulário e o mapa das ferramentas, e construir exemplos concretos o suficiente para você começar. Serei honesto também sobre outra coisa: integrar bibliotecas externas em C++ (baixá-las, compilá-las, linká-las via CMake) é, tradicionalmente, uma das partes mais irritantes da linguagem — não há um gerenciador de pacotes universal como o pip do Python, embora o vcpkg e o Conan (que mencionei no A Jornada Completa) tenham melhorado muito isso. Vou usar bibliotecas simples de integrar (SQLite, que é quase só um arquivo; cpp-httplib, de cabeçalho único) justamente para que o foco fique nos conceitos, não na luta com o sistema de build. O objetivo é a ponte, não a enciclopédia.

C++ na web é escolha de requisito, não de gosto: compensa onde desempenho por requisição, controle de memória ou integração com código nativo pesam mais que velocidade de desenvolvimento, e não compensa quando o gargalo é entregar rápido uma aplicação comum. Vale conhecer a fronteira antes de atravessá-la — o ecossistema é menor, e muito do que outras linguagens dão pronto aqui é decisão a tomar.

Fontes e leituras recomendadas

  • sqlite.org: o site do SQLite, com sua documentação e a filosofia do banco embutido — leitura para o próximo artigo.
  • github.com/yhirose/cpp-httplib: a biblioteca HTTP de cabeçalho único que usaremos, com exemplos no README.
  • pqxx.org (libpqxx) e dev.mysql.com/doc/connector-cpp: as documentações dos conectores de PostgreSQL e MySQL, para quando você for além do SQLite.
  • mongocxx.org: o driver C++ do MongoDB, para o mundo dos bancos de documentos.
  • vcpkg.io: o gerenciador de pacotes que simplifica a integração de todas essas bibliotecas em projetos CMake.

Exercícios

Exercício 1

Explique, com suas palavras, em que cenário concreto o C++ seria uma boa escolha para um backend web, e em que cenário uma linguagem como Python seria mais apropriada. Justifique com base no requisito dominante de cada caso.

Ver resposta

✓ Resposta: C++ é boa escolha para um backend web quando o requisito dominante é desempenho ou controle de recursos: por exemplo, um serviço que processa milhões de requisições por segundo com latência mínima (um sistema de leilão em tempo real, um gateway de alta frequência, um motor de busca), ou que faz trabalho computacional pesado por requisição (processamento de imagem, simulação). Aí a eficiência e o controle de memória do C++ justificam sua maior complexidade de desenvolvimento. Python (ou similar) é mais apropriado quando o requisito dominante é velocidade de desenvolvimento e produtividade: um site institucional, um CRUD de administração, um protótipo, uma aplicação onde a lógica de negócio muda rápido e o volume de tráfego é modesto — ali, os frameworks maduros e a menor cerimônia de Python entregam valor muito mais rápido, e o desempenho do C++ seria um ganho que não se paga frente ao custo de desenvolvimento. A regra: performance crítica → C++; agilidade → linguagem de alto nível.

Exercício 2

Descreva a diferença fundamental entre o SQLite e o PostgreSQL quanto à arquitetura (embutido versus servidor). Para uma aplicação de desktop que guarda dados localmente, qual seria mais adequado e por quê?

Ver resposta

✓ Resposta: O SQLite é embutido: ele é uma biblioteca que roda dentro do processo da sua aplicação e guarda o banco inteiro num único arquivo no disco local — não há um servidor separado, nenhum processo à parte, nenhuma configuração de rede. O PostgreSQL é servidor: ele roda como um processo (ou máquina) separado, ao qual sua aplicação se conecta pela rede, e pode atender múltiplos clientes simultâneos, com gestão sofisticada de concorrência, usuários e recursos. Para uma aplicação de desktop que guarda dados localmente, o SQLite é mais adequado: não exige instalar nem administrar um servidor, o banco é só um arquivo que acompanha a aplicação (fácil de distribuir e fazer backup), e a simplicidade casa com o uso local de um único usuário. O PostgreSQL seria exagero — sua robustez de servidor multiusuário é desnecessária e sua administração seria um fardo para um app de desktop. SQLite para local e embutido; PostgreSQL para servidores multiusuário em rede.

Exercício 3

Nosso banco do capstone era em memória; um banco real persiste em disco. Liste três consequências práticas dessa diferença (por exemplo, o que acontece ao desligar) e como cada uma afeta o design de uma aplicação.

Ver resposta

✓ Resposta: Três consequências de persistir em disco versus memória: (a) Durabilidade — dados em disco sobrevivem ao desligamento do programa e da máquina, enquanto os em memória se perdem; isso significa que a aplicação precisa carregar os dados do disco ao iniciar e salvá-los ao modificar, adicionando etapas de leitura/escrita ao design. (b) Desempenho — acesso a disco é ordens de magnitude mais lento que a RAM, então uma aplicação com banco em disco precisa pensar em quando ler/escrever (evitar acessos desnecessários, usar cache em memória para dados quentes, agrupar escritas), enquanto o banco em memória tinha acesso uniforme e rápido. (c) Integridade sob falha — se o programa trava no meio de uma escrita em disco, o arquivo pode ficar corrompido; bancos reais usam transações (garantindo que uma operação ou completa inteira ou não acontece) para proteger a consistência, um conceito que o nosso banco em memória não precisou enfrentar. Cada uma dessas afeta o design: durabilidade exige lógica de carga/salvamento, desempenho exige estratégia de acesso, e integridade exige transações.

Exercício 4

Um conector de banco de dados abre uma conexão que precisa ser fechada. Explique como o RAII (Fase 2) se aplica a isso, e esboce a estrutura de uma classe Conexao que envolva uma conexão de banco de forma segura.

Ver resposta

✓ Resposta: Uma conexão de banco é um recurso clássico para RAII: você a adquire (abre) e deve liberá-la (fechar), e esquecer o fechamento vaza a conexão (um recurso finito e caro). O RAII envolve a conexão num objeto que a abre no construtor e a fecha no destrutor, garantindo o fechamento em qualquer caminho de saída, inclusive por exceção. Esboço:

class Conexao {
public:
    Conexao(const std::string& caminho) {
        // abre a conexão (ex.: sqlite3_open); lança se falhar
        if (abrir_falhou) throw std::runtime_error("não conectou");
    }
    ~Conexao() {
        fechar();   // fecha a conexão automaticamente ao sair de escopo
    }
    // Proíbe cópia (uma conexão tem um dono único) — ou implementa movimento.
    Conexao(const Conexao&) = delete;
    Conexao& operator=(const Conexao&) = delete;

    void executar(const std::string& sql);   // usa a conexão
private:
    /* handle da conexão (ex.: sqlite3*) */
    void fechar();
};
// Uso: { Conexao c("dados.db"); c.executar(...); }  // fecha sozinha no }

A conexão é aberta no construtor, fechada no destrutor, e a cópia é proibida (posse única, como o unique_ptr — Fase 2), garantindo que nunca haja dois donos tentando fechar a mesma conexão.

Exercício 5

A aula afirma que "um servidor web em C++ é uma aplicação concorrente que usa tudo do curso". Escolha três fases do curso e explique concretamente como cada uma se aplicaria à construção de um servidor HTTP que atende múltiplas requisições simultâneas.

Ver resposta

✓ Resposta: Três fases aplicadas a um servidor HTTP concorrente: Fase 7 (Concorrência) — o servidor atende múltiplas requisições simultâneas, tipicamente com um pool de threads onde cada thread processa uma requisição; toda a disciplina de mutexes e atômicos que aprendemos protege dados compartilhados entre requisições (por exemplo, um contador de acessos, ou um cache compartilhado), evitando as corridas de dados que corromperiam o estado do servidor. Fase 2 (RAII) — cada requisição adquire recursos (conexões de banco, buffers, arquivos) que devem ser liberados ao fim de seu processamento; envolvê-los em objetos RAII garante que, mesmo se o processamento de uma requisição lançar uma exceção, todos os recursos daquela requisição sejam liberados, sem vazamento — crucial num servidor que roda por dias atendendo milhões de requisições, onde qualquer vazamento se acumularia até derrubá-lo. Fase 6 (Tratamento de erros) — requisições falham de muitas formas (dados inválidos, banco indisponível, recurso não encontrado); modelar essas falhas com expected ou exceções permite ao servidor responder com o código HTTP apropriado (400, 500, 404) em vez de travar, e a aliança exceção-RAII garante que uma falha no meio do processamento não vaze recursos nem corrompa o estado compartilhado. Juntas, essas fases fazem um servidor robusto: concorrente para escalar, seguro em recursos para durar, e resiliente a erros para não cair.

Comentários

Mais em Linguagem C++

Do Comando Único ao Projeto Real — Construindo com CMake
Do Comando Único ao Projeto Real — Construindo com CMake

Compilar com uma linha de g++ deixa de servir bem antes do esperado: bastam…

Posse Compartilhada e seus Perigos — shared_ptr e weak_ptr
Posse Compartilhada e seus Perigos — shared_ptr e weak_ptr

Quando várias partes do programa precisam legitimamente compartilhar um…

Extensão — O Terminal Vira Rede: Um Servidor HTTP Mínimo
Extensão — O Terminal Vira Rede: Um Servidor HTTP Mínimo

Um programa de terminal vira servidor HTTP com uma biblioteca de cabeçalho…