CodeGym /Cursos /C++ SELF /RAII e fechamento correto

RAII e fechamento correto

C++ SELF
Nível 63, Lição 0
Disponível

1. Para que precisamos de arquivos, se existe std::cin?

Quando você escreve programas de estudo, parece que a entrada pelo teclado será suficiente para sempre. Mas imagine um usuário real: ele adicionou 20 tarefas à lista, fechou o programa, abriu novamente… e viu tudo vazio. Isso é frustrante, mesmo se o usuário for você. Arquivos permitem que a aplicação “lembre” do estado entre execuções: salvar configurações, dados, resultados de cálculo e outras coisas importantes.

Em C++ os arquivos são conectados ao programa através da interface de streams — muito parecida com std::cin e std::cout. Não vamos aqui aprofundar em formatos e tratamento de erros de leitura (isso é assunto à parte), mas vamos aprender o essencial: como abrir corretamente um arquivo, verificar que ele abriu, e garantir o fechamento correto via RAII.

Fluxos de arquivo de <fstream>

Se <iostream> nos dá streams para console, <fstream> fornece os streams para arquivos. Em termos de “quem faz o que” é bem simples: existe um stream para leitura, um para escrita e um para ambos. É importante não confundir suas funções — mais ou menos como não confundir chave de fenda e martelo. Às vezes você até consegue usar a chave de fenda para pregar um prego… mas é melhor não fazer isso.

Classe do stream Propósito Anologia simples
std::ifstream
ler do arquivo “como std::cin, só que de arquivo”
std::ofstream
escrever no arquivo “como std::cout, só que para arquivo”
std::fstream
ler e escrever “um cabo universal, mas pense antes de usar”

O “esqueleto” mínimo de um programa com arquivos é assim:

#include <fstream>
#include <iostream>

int main() {
    std::ifstream in("input.txt");
    std::ofstream out("output.txt");

    std::cout << "Streams criados\n"; // Streams created
}

Por enquanto não verificamos nada — isso é intencional. Agora vamos adicionar segurança.

2. Abrindo arquivo e checando o resultado

Quando você encontra fluxos de arquivo pela primeira vez, surge a pergunta: “Qual a forma correta de abrir um arquivo?” Boa notícia: existem exatamente duas formas “normais”, e ambas são usadas em código real. A diferença não é mágica, é conveniência: às vezes o nome do arquivo é conhecido imediatamente, às vezes vem de uma variável (por exemplo, das configurações) e aí faz sentido abrir mais tarde.

Abrir no construtor

Esse é o modo mais comum: criamos o stream e já passamos o nome do arquivo.

#include <fstream>
#include <iostream>

int main() {
    std::ifstream in("notes.txt");
    if (!in) {
        std::cerr << "Não é possível abrir notes.txt\n";
        return 1;
    }
    std::cout << "Arquivo aberto\n"; // File opened
}

Aqui já fica clara uma coisa importante: o stream pode ser verificado como bool.

Abrir depois via open()

Essa opção é útil quando o nome do arquivo é calculado ou escolhido mais tarde.

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::string filename = "notes.txt";

    std::ifstream in;
    in.open(filename);

    if (!in) {
        std::cerr << "Não é possível abrir " << filename << "\n";
        return 1;
    }
}

As duas formas são equivalentes. Para quem está começando, “no construtor” costuma ser mais legível.

Como checar a abertura: if(!stream) e is_open()

Há uma verdade dura sobre arquivos: o arquivo pode não abrir. As razões costumam ser banais: arquivo não existe, sem permissões, caminho incorreto, ou você esperava um diretório diferente ao executar o programa. Se você não checar a abertura, leitura/escrita simplesmente “não vão funcionar”, e você ficará olhando para o código pensando “mas eu escrevi certo”.

Checar com if(!stream) é a forma mais prática, porque verifica o “estado geral” do stream:

#include <fstream>
#include <iostream>

int main() {
    std::ofstream out("log.txt");
    if (!out) {
        std::cerr << "Não é possível abrir log.txt para escrita\n";
        return 1;
    }
    out << "Olá arquivo\n";
}

Checar com is_open() é um pouco mais específico: verifica exatamente “o arquivo está aberto?”.

#include <fstream>
#include <iostream>

int main() {
    std::ifstream in("data.txt");
    if (!in.is_open()) {
        std::cerr << "data.txt não está aberto\n";
        return 1;
    }
}

Ambas as formas são aceitáveis. Em tarefas didáticas costuma-se usar if(!in), porque depois geralmente se verificam outros estados do stream também.

4. RAII e fechamento correto do arquivo

Quando você ouve “fechar arquivo”, é fácil imaginar que é uma operação manual obrigatória, como “colocar ponto e vírgula”. Mas em C++ os streams de arquivo são feitos para fecharem automaticamente: quando o objeto do stream sai do escopo, seu destrutor é chamado e o arquivo é fechado.

Isso é RAII puro: Resource Acquisition Is Initialization — o recurso é adquirido na criação do objeto e liberado quando o objeto é destruído.

Imagine que o stream é um “funcionário” a quem você deu a chave da sala (arquivo). Enquanto o funcionário trabalha (o objeto vive), a chave está com ele. Assim que o funcionário vai embora (o objeto sai do scope), a chave é entregue à segurança automaticamente. Você não precisa sair correndo e gritar “Devolvam as chaves!”.

Esquematicamente isso fica assim:

flowchart TD
    A["Criado std::ofstream out('tasks.txt')"] --> B["Escrevemos dados out << ..."]
    B --> C["Saímos do bloco { } ou da função"]
    C --> D["O destrutor de out é chamado automaticamente"]
    D --> E["Arquivo fechado corretamente (RAII)"]

E sim: mesmo que você saia da função via return, o RAII ainda age. Por isso RAII é um dos maiores “antiestresse” do C++.

Então precisamos de close()?

Após dizer “fecha sozinho” surge a pergunta: “Então por que existe close()?” Ele existe porque às vezes é necessário fechar o arquivo antes do fim da função ou do bloco. Por exemplo, você quer gravar um arquivo, fechá‑lo, e depois abrir outro arquivo com o mesmo objeto de stream — ou reabrir o mesmo arquivo em outro modo.

Regra simples para iniciantes: se você não consegue explicar imediatamente por que precisa de close(), provavelmente não precisa dele.

Exemplo onde close() faz sentido: reabrir o stream para outro arquivo.

#include <fstream>
#include <iostream>

int main() {
    std::ofstream out("a.txt");
    if (!out) return 1;

    out << "A\n";
    out.close(); // fechou explicitamente

    out.open("b.txt");
    out << "B\n";
}

Mas se você só escreve em um arquivo e sai da função — não complique: RAII já faz tudo certo.

5. Mini‑prática: salvando tarefas em arquivo

Para não ficar tudo teórico, vamos continuar a ideia de exemplo: um pequeno app de console “lista de tarefas”. Suponha que já temos o modelo Task e uma lista std::vector<Task>, e que os comandos de adicionar/exibir foram feitos antes. Agora vamos adicionar a salvação.

Nesta aula vamos focar não no formato ou parsing, mas na disciplina: abrir → checar → escrever → sair da função (e fechar automaticamente).

Modelo da tarefa

#include <string>

struct Task {
    int id{};
    bool done{};
    std::string title;
};

Função de salvar save_tasks

#include <fstream>
#include <string>
#include <vector>

bool save_tasks(const std::string& filename, const std::vector<Task>& tasks) {
    std::ofstream out(filename);
    if (!out) return false;

    for (const Task& t : tasks) {
        out << t.id << ' ' << t.done << ' ' << t.title << '\n';
    }
    return true;
}

Observe: não chamamos out.close() em nenhum lugar. Não precisamos. Assim que a função terminar, out desaparecerá e o arquivo será fechado.

Uso em main()

#include <iostream>
#include <vector>

int main() {
    std::vector<Task> tasks = { {1, false, "Buy milk"}, {2, true, "Learn C++"} };

    if (!save_tasks("tasks.txt", tasks)) {
        std::cerr << "Falha ao salvar\n";
        return 1;
    }

    std::cout << "Salvo!\n"; // Saved!
}

Isso já é um passo útil: agora o programa pode deixar uma marca no mundo externo.

6. Diretório de trabalho: onde o arquivo fica

Quando você abre "tasks.txt", quase sempre usa um caminho relativo. A pergunta “onde exatamente será criado esse arquivo?” vira um pequeno enigma: depende de qual é a diretoria de trabalho a partir da qual o programa é executado. Na Web‑IDE é uma coisa, na IDE é outra, e ao executar no terminal é outra ainda.

Portanto, ao depurar é útil lembrar: se o arquivo “não aparece”, muitas vezes o problema não é o std::ofstream, e sim o caminho. Nos estágios iniciais é mais simples usar arquivos próximos ao executável do projeto (conforme o ambiente) e imprimir uma mensagem clara caso a abertura falhe. Depois, quando houver bibliotecas de manipulação de caminhos, isso fica mais fácil — hoje o foco é streams e RAII.

7. std::fstream: stream para leitura e escrita

Às vezes você quer “ler e escrever no mesmo arquivo”. Para isso existe std::fstream. Mas, como um canivete suíço, ele é ótimo até o momento em que você tenta usar uma ferramenta errada para outra tarefa. Ou seja: use quando for apropriado.

Por ora é suficiente saber que fstream existe e pode ser aberto para leitura e escrita. Nesta aula não vamos discutir modos de abertura, apenas mostrar um exemplo mínimo de criação.

#include <fstream>
#include <iostream>

int main() {
    std::fstream io("tasks.txt"); // por padrão o comportamento depende da implementação e do modo
    if (!io) {
        std::cerr << "Não é possível abrir tasks.txt\n";
        return 1;
    }
}

No código real costuma‑se abrir fstream com modos explícitos para não ficar em dúvida sobre o que acontece com o arquivo.

8. Erros típicos

Erro nº 1: “Abri o arquivo, então ele abriu”.
Um iniciante frequentemente escreve std::ifstream in("data.txt"); e já faz a leitura sem checar o estado do stream. Se o arquivo não abriu, a leitura simplesmente não vai acontecer e o código parecerá “funcionar”. Remédio: logo após abrir, faça if(!in) { ... } e imprima uma mensagem clara.

Erro nº 2: fechamento manual do arquivo “só por garantia” em todo lugar do código.
Às vezes, após conhecer close(), dá vontade de colocá‑lo em todo canto, como alho na comida “por precaução”. O resultado são saídas extras e em algum lugar você pode fechar o stream cedo demais e se surpreender por que a escrita não funciona. Se você não precisa fechar o arquivo antes do fim do scope, confie no RAII.

Erro nº 3: scope do stream demasiado amplo.
O stream é declarado no começo do main, usado numa pequena parte, e depois “fica pendurado” até o fim do programa. Isso torna o código menos claro e aumenta a chance de uso indevido. Bom hábito: manter o stream no escopo onde realmente é necessário — às vezes até num bloco { ... } separado.

Erro nº 4: confundir os papéis de ifstream e ofstream.
Na pressa é fácil declarar std::ifstream out("file.txt") e depois estranhar erros ou falta de resultado. O compilador nem sempre “entende” sua intenção. Nomeie variáveis de forma honesta (in, out, io) e escolha o tipo de stream conforme a tarefa: leitura — ifstream, escrita — ofstream, ambos — fstream.

Erro nº 5: esperar que o arquivo esteja sempre ao lado e ignorar o diretório de trabalho.
Muito comum: “o arquivo existe no projeto, mas o programa diz que não pode abrir”. Frequentemente a razão é o diretório atual incorreto ao executar. Nessa fase basta lembrar: caminhos relativos dependem de onde você executou o programa. Assim, falha ao abrir é um cenário normal, não motivo para “quebrar tudo sem avisar”.

1
Tarefa
C++ SELF, nível 63, lição 0
Bloqueado
Registro de execução
Registro de execução
1
Tarefa
C++ SELF, nível 63, lição 0
Bloqueado
Total do arquivo
Total do arquivo
1
Tarefa
C++ SELF, nível 63, lição 0
Bloqueado
Cópia numerada
Cópia numerada
1
Tarefa
C++ SELF, nível 63, lição 0
Bloqueado
Um escritor
Um escritor
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION