Close Menu
GeekBlog

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    How to Overload an Operator in C++ (Complete Guide)

    July 30, 2026

    How to Use Average True Range (ATR): Beginner’s Guide

    July 30, 2026

    How to Interpret Candlestick Patterns (Not Just Name Them)

    July 30, 2026
    Facebook X (Twitter) Instagram Threads
    GeekBlog
    • Home
    • Mobile
    • Tech News
    • Blog
    • How-To Guides
    • AI & Software
    Facebook
    GeekBlog
    Home»Blog»How to Write a File in C++ (With Working Examples)
    Blog

    How to Write a File in C++ (With Working Examples)

    Ethan CaldwellBy Ethan CaldwellAugust 2, 2025Updated:July 30, 202615 Mins Read
    Share Facebook Twitter Pinterest LinkedIn Tumblr Email Copy Link
    C++ source code on a monitor showing how to write a file in C++ with ofstream
    Share
    Facebook Twitter LinkedIn Pinterest Email Copy Link

    To write a file in C++, include <fstream>, construct a std::ofstream with the file name, check that it opened, then send data with <<. The destructor closes and flushes the file for you when the object goes out of scope, so you rarely need to call close() at all.

    Quick answer: 1. #include <fstream>. 2. std::ofstream out("notes.txt"); 3. if (!out) { /* handle the failure */ } 4. out << "text"; 5. Let the destructor close it. Skipping step 3 is the single most common reason a program reports success and writes nothing.

    Every example below was compiled with g++ -std=c++20 -Wall -Wextra -pedantic on GCC 13.3 and run, and the output you see is the real output. The one C++23 example is marked as such. If you are new to C++ file I/O, read the sections in order. If you already write files and something is broken, jump to the troubleshooting section.

    The shortest version that works

    Here is the whole idea in twelve lines. Note the error check on line five, which most tutorials leave out.

    #include <fstream>
    #include <iostream>
    
    int main() {
        std::ofstream out("notes.txt");
        if (!out) {
            std::cerr << "Could not open notes.txt for writing\n";
            return 1;
        }
        out << "First line\n";
        out << "Second line, number = " << 42 << '\n';
        // out closes itself here, flushing whatever is still buffered
        std::cout << "Wrote notes.txt\n";
    }

    std::ofstream is an output file stream. It behaves like std::cout, which means every operator<< you already know works on it, including the ones you write yourself. Making your own types printable and writable is what overloading the stream insertion operator in C++ is for.

    Three ways to open the file

    The constructor form is the one to use by default. The .open() form exists for cases where you do not know the file name yet. The third form is not a different API at all, just a scope you create on purpose so the file closes at a point you control.

    #include <fstream>
    #include <iostream>
    #include <string>
    
    int main() {
        // 1. Constructor opens the file
        std::ofstream a("a.txt");
    
        // 2. Default-construct, then .open() later
        std::ofstream b;
        b.open("b.txt", std::ios::app);
    
        // 3. RAII scope: the file is closed at the closing brace
        {
            std::ofstream c("c.txt");
            c << "written and closed before the next line runs\n";
        }
        std::ifstream check("c.txt");
        std::string line;
        std::getline(check, line);
        std::cout << "c.txt contains: " << line << '\n';
    
        if (a && b) std::cout << "a.txt and b.txt opened fine\n";
    }

    That prints c.txt contains: written and closed before the next line runs, which proves the point. Because the braces ended, the data was flushed and the file was closed before the read happened.

    Open modes and what each one really does

    A second argument to the constructor or to .open() controls how the file is opened. These are bit flags, so you combine them with |.

    FlagWhat it doesMissing file?
    std::ios::outDefault for ofstream. Opens for writing and truncates existing content to zero bytes.Created
    std::ios::appEvery write goes to the end of the file, even if you seek elsewhere first. The mode for log files.Created
    std::ios::truncExplicitly empties the file. Redundant with a plain out, useful combined with in|out.Created
    std::ios::binaryNo character translation. On Windows this is what stops '\n' becoming CRLF.Created
    std::ios::ateSeeks to the end once, at open time. Later seeks move freely, so this is not the same as app.Depends on the other flags
    std::ios::in | std::ios::outRead and write in place, overwriting from the current position and keeping the rest of the file.Open fails

    The last two rows surprise people, so here they are running. Each dump() call prints the file contents with each line in brackets.

    #include <fstream>
    #include <iostream>
    #include <string>
    
    void dump(const char* label, const char* path) {
        std::ifstream in(path);
        std::cout << label << ": ";
        std::string line;
        while (std::getline(in, line)) std::cout << '[' << line << ']';
        std::cout << '\n';
    }
    
    int main() {
        { std::ofstream f("log.txt"); f << "run 1\n"; }        // default: out|trunc
        dump("after first write   ", "log.txt");
    
        { std::ofstream f("log.txt"); f << "run 2\n"; }        // truncated again
        dump("after plain reopen  ", "log.txt");
    
        { std::ofstream f("log.txt", std::ios::app); f << "run 3\n"; }
        dump("after ios::app      ", "log.txt");
    
        // ios::ate seeks to the end but does NOT force later writes to the end
        { std::ofstream f("log.txt", std::ios::in | std::ios::out | std::ios::ate);
          f << "run 4\n"; }
        dump("after in|out|ate    ", "log.txt");
    
        // in|out without ate overwrites from byte 0 and keeps the rest
        { std::ofstream f("log.txt", std::ios::in | std::ios::out);
          f << "XX\n"; }
        dump("after in|out at 0   ", "log.txt");
    }
    after first write   : [run 1]
    after plain reopen  : [run 2]
    after ios::app      : [run 2][run 3]
    after in|out|ate    : [run 2][run 3][run 4]
    after in|out at 0   : [XX][ 2][run 3][run 4]

    Look at that last line. Writing three bytes at position zero replaced run with XX plus a newline, leaving 2 stranded on its own line. Overwrite mode does not shift bytes for you. And opening a file that does not exist with in | out fails outright, which catches people who expect it to behave like plain out.

    Writing with <<, write() and put()

    Three different jobs. operator<< formats a value as text and honors manipulators and the stream’s locale. put() writes exactly one character with no formatting. write() pushes a block of bytes and adds nothing, not even a terminator.

    Recommended for you:

    How to Optimize Website Content for Search Engines to Boost Traffic and Rankings
    Blog·Aug 2, 2025

    How to Optimize Website Content for Search Engines to Boost Traffic and Rankings

    #include <fstream>
    #include <iostream>
    #include <string>
    
    int main() {
        std::ofstream out("modes.txt");
    
        // operator<< : formatted text output, applies locale and manipulators
        out << "count=" << 7 << " ratio=" << 0.5 << '\n';
    
        // put() : one character, unformatted
        out.put('X');
        out.put('\n');
    
        // write() : raw block of bytes, no formatting, no null terminator
        std::string blob = "raw bytes, no newline added";
        out.write(blob.data(), static_cast<std::streamsize>(blob.size()));
        out.put('\n');
    
        out.close();
    
        std::ifstream in("modes.txt");
        std::string line;
        while (std::getline(in, line)) std::cout << "[" << line << "]\n";
    }
    Tip: Prefer '\n' over std::endl. Both write a newline, but std::endl also flushes the stream. Flushing on every line turns one buffered write into thousands of syscalls, which is a measurable slowdown on large files and buys you nothing the destructor does not already do.

    Flushing, closing, and what RAII buys you

    An ofstream buffers. Data you write sits in memory until the buffer fills, you call flush(), you call close(), or the object is destroyed. This program checks the on-disk size before and after a flush.

    #include <fstream>
    #include <iostream>
    
    int main() {
        std::ofstream out("buffered.txt");
        out << "line written to the buffer\n";
    
        // Nothing is on disk yet on most implementations.
        std::ifstream peek1("buffered.txt", std::ios::ate);
        std::cout << "before flush: " << peek1.tellg() << " bytes on disk\n";
    
        out.flush();
    
        std::ifstream peek2("buffered.txt", std::ios::ate);
        std::cout << "after flush:  " << peek2.tellg() << " bytes on disk\n";
    }

    On GCC 13.3 that prints before flush: 0 and after flush: 27. This is the mechanism behind the classic empty-file bug: the program crashed, called std::exit, or was killed before the destructor ran. RAII covers the normal path and the exception path, because destructors run during stack unwinding. It cannot help if the process dies outright, so for long-running writers, flush at meaningful checkpoints.

    Writing formatted numbers and a vector of records

    Default stream formatting drops trailing zeros, so 92500.0 lands in the file as 92500. If another program parses the file, pin the format down with std::fixed and std::setprecision from <iomanip>.

    #include <fstream>
    #include <iomanip>
    #include <iostream>
    #include <string>
    #include <vector>
    
    struct Employee {
        std::string name;
        int         id{};
        double      salary{};
    };
    
    int main() {
        std::vector<Employee> staff = {
            {"Dana Whitfield", 1041, 92500.0},
            {"Miguel Ortiz",   1042, 78250.5},
            {"Priya Raman",    1043, 105000.0}
        };
    
        std::ofstream out("staff.csv");
        if (!out) {
            std::cerr << "cannot open staff.csv\n";
            return 1;
        }
    
        // Without this, 92500.0 is written as "92500" and 105000.0 as "105000".
        out << std::fixed << std::setprecision(2);
    
        out << "name,id,salary\n";
        for (const Employee& e : staff)
            out << e.name << ',' << e.id << ',' << e.salary << '\n';
    
        out.close();
        if (!out) {
            std::cerr << "write or close failed\n";
            return 1;
        }
    
        std::ifstream in("staff.csv");
        std::string line;
        while (std::getline(in, line)) std::cout << line << '\n';
    }

    Notice the second error check, after close(). A stream can open fine and still fail mid-write, most often because the disk filled or a quota was hit. The reverse trip, turning that CSV back into objects, is covered in storing text file data into a vector in C++.

    Binary writes, and the portability caveat

    For a compact file you can reload quickly, write the raw object bytes. The trick is reinterpret_cast to const char*, because write() deals in characters.

    #include <cstdint>
    #include <fstream>
    #include <iostream>
    #include <vector>
    
    struct Sample {
        std::int32_t id;
        double       value;
    };
    
    int main() {
        std::vector<Sample> samples = {{1, 3.5}, {2, -0.25}, {3, 1e6}};
    
        std::ofstream out("samples.bin", std::ios::binary);
        if (!out) return 1;
        out.write(reinterpret_cast<const char*>(samples.data()),
                  static_cast<std::streamsize>(samples.size() * sizeof(Sample)));
        out.close();
    
        std::ifstream in("samples.bin", std::ios::binary);
        std::vector<Sample> back(samples.size());
        in.read(reinterpret_cast<char*>(back.data()),
                static_cast<std::streamsize>(back.size() * sizeof(Sample)));
    
        std::cout << "sizeof(Sample) = " << sizeof(Sample) << " bytes\n";
        for (const Sample& s : back)
            std::cout << s.id << " -> " << s.value << '\n';
    }
    Heads up: That file is only readable by a build with the same endianness, the same type sizes and the same struct padding. sizeof(Sample) printed 16 here, not 12, because of the four padding bytes before the double. Change a compiler flag, a member order, or the target architecture and the file becomes garbage. The technique is also only defined for trivially copyable types, so a struct containing a std::string is out. Use it for a local cache you can regenerate, never for an interchange format.

    Paths and directories with std::filesystem

    Since C++17, <filesystem> handles path joining, directory creation and size checks without platform #ifdef blocks. ofstream accepts a fs::path directly. Note that ofstream will never create a missing directory for you.

    #include <filesystem>
    #include <fstream>
    #include <iostream>
    
    namespace fs = std::filesystem;
    
    int main() {
        fs::path dir = fs::current_path() / "reports" / "2026-07";
        std::error_code ec;
        fs::create_directories(dir, ec);
        if (ec) {
            std::cerr << "could not create " << dir << ": " << ec.message() << '\n';
            return 1;
        }
    
        fs::path file = dir / "summary.txt";
        std::ofstream out(file);
        if (!out) {
            std::cerr << "could not open " << file << '\n';
            return 1;
        }
        out << "report body\n";
        out.close();
    
        std::cout << "absolute path: " << fs::absolute(file) << '\n';
        std::cout << "exists: " << std::boolalpha << fs::exists(file) << '\n';
        std::cout << "size: " << fs::file_size(file) << " bytes\n";
    }

    Use the / operator to join path components rather than concatenating strings. It picks the right separator per platform, so you never hand-escape a Windows path like C:\\data\\out.txt. If you do need a literal Windows path in source, either double every backslash or use a raw string literal: R"(C:\data\out.txt)". The full reference is the cppreference filesystem library documentation.

    Turning stream errors into exceptions

    If you would rather not check a return value after every operation, tell the stream to throw. Pass a mask of the states you care about to exceptions(). failbit covers logical failures like a failed open; badbit covers unrecoverable I/O errors.

    #include <fstream>
    #include <iostream>
    
    int main() {
        std::ofstream out;
        out.exceptions(std::ios::failbit | std::ios::badbit);
    
        try {
            out.open("/definitely/not/a/real/dir/data.txt");
            out << "this line never runs\n";
        } catch (const std::ios_base::failure& e) {
            std::cerr << "stream error: " << e.what() << '\n';
            std::cerr << "code: " << e.code().message() << '\n';
            return 1;
        }
    }

    Be warned that the message quality is poor. On GCC 13.3 this prints basic_ios::clear: iostream error, which tells you nothing about which file or why. That is a long-standing libstdc++ limitation. When you need a real reason, check errno or use std::filesystem calls, which report a proper std::error_code.

    ofstream vs fprintf vs std::format vs std::print

    Some readers searching for this actually mean C, not C++. The C way still compiles in every C++ program and is worth knowing, because plenty of existing code uses it.

    #include <cstdio>
    
    int main() {
        std::FILE* f = std::fopen("legacy.txt", "w");
        if (!f) {
            std::perror("fopen");
            return 1;
        }
        std::fprintf(f, "id=%d name=%s ratio=%.3f\n", 17, "sensor-a", 0.4267);
        std::fputs("second line\n", f);
        if (std::fclose(f) != 0) {
            std::perror("fclose");
            return 1;
        }
    
        f = std::fopen("legacy.txt", "r");
        char buf[128];
        while (std::fgets(buf, sizeof buf, f)) std::fputs(buf, stdout);
        std::fclose(f);
    }

    And here is the C++23 option, which pairs printf-style brevity with compile-time checked format strings. It needs GCC 14 or newer, or MSVC 19.37 or newer, because that is when <print> landed in the standard libraries. This one was compiled with g++-14 -std=c++23.

    #include <cstdio>
    #include <print>
    
    int main() {
        std::FILE* f = std::fopen("out.txt", "w");
        if (!f) return 1;
    
        std::print(f, "id={} value={:.2f}\n", 7, 3.14159);
        std::println(f, "second line");
    
        if (std::fclose(f) != 0) return 1;
        std::println("wrote out.txt");
    }
    ApproachSinceFormat string checked?Use it when
    std::ofstream with <<C++98Type-safe by constructionDefault choice. Works everywhere, extensible to your own types, RAII closes it.
    std::fprintfC89Only via compiler warningsYou are writing C, or maintaining code that already uses FILE*.
    std::format into a streamC++20Yes, at compile timeYou want tidy layout control today and cannot require a C++23 library.
    std::print / std::printlnC++23Yes, at compile timeGCC 14+ or MSVC 19.37+. Shortest correct code, and Unicode-aware on Windows consoles.

    Troubleshooting the five failures that cost the most time

    The file is created but empty. You never flushed and the destructor never ran, usually because of a crash, a std::exit call, or an abort while the stream was still alive. Add an explicit out.flush() at a checkpoint and see if the data appears.

    Permission denied. The open silently sets failbit and you did not check it. Confirm with if (!out), then check the directory permissions rather than the file’s. You need write and execute on the containing directory to create a file there. Writing under /var or C:\Program Files without elevation fails for this reason.

    Recommended for you:

    Where Are WordPress Plugin Settings Stored? (Full Answer)
    Blog·Aug 2, 2025

    Where Are WordPress Plugin Settings Stored? (Full Answer)

    Extra carriage returns, or missing ones. In text mode on Windows, every '\n' you write becomes CRLF on disk. On Linux and macOS it stays LF. If a checksum differs between platforms, or a parser sees stray \r characters, open with std::ios::binary and write the exact bytes you intend.

    The file went somewhere you cannot find. A relative path resolves against the process working directory, not the source file and not the executable. Run std::cout << std::filesystem::current_path() once and the mystery ends. IDEs are the usual culprit: Visual Studio defaults the working directory to the project folder, not the Debug output folder.

    You appended when you meant to truncate, or the reverse. Plain std::ofstream out(path) destroys the old contents with no warning. If a run wiped a file you cared about, that is why. Conversely, if a file keeps growing across runs, something is passing std::ios::app.

    Frequently asked questions

    Do I need to call close() on an ofstream?

    No, in normal code. The destructor closes and flushes when the object goes out of scope, including when an exception unwinds the stack. Call close() explicitly only when you want to check the result of the close, or when you need the file released before the scope ends so another process can read it.

    How do I append to a file instead of overwriting it in C++?

    Pass std::ios::app as the second argument: std::ofstream log("app.log", std::ios::app);. Every write then goes to the end of the file regardless of any seek you perform, which makes it the correct mode for logs. If the file does not exist, it is created.

    Why is my C++ output file empty even though the program ran?

    Almost always buffering. The data was still in memory when the process ended abnormally, so nothing reached disk. Let the ofstream go out of scope normally, or call flush() at checkpoints. The other cause is a failed open you never checked, which leaves an empty or missing file.

    Can std::ofstream create a directory that does not exist?

    No. Opening reports/out.txt when reports/ is missing simply fails. Call std::filesystem::create_directories(path.parent_path()) first, then open the file. That function creates every missing level and returns quietly if the directory already exists.

    Should I use ofstream or fprintf for writing files in C++?

    Use ofstream. It is type-safe, it closes itself, and it works with operators you define for your own classes. Reach for fprintf only in C code or an existing FILE* codebase. On a C++23 toolchain, std::print gives you the concise syntax without the type-safety hole.

    What is the fastest way to write a large file in C++?

    Open in binary mode, avoid std::endl, and batch your data into fewer, larger write() calls instead of many small << operations. Enlarging the stream buffer with pubsetbuf before opening can help too, though the gains are implementation-dependent.

    Wrapping up

    For nearly every case, the answer is four lines: construct an ofstream, check it with if (!out), write with <<, and let scope close it. Add std::ios::app for logs, std::ios::binary when the exact bytes matter, and std::filesystem whenever a path has more than one component.

    The part worth internalizing is the error check. It costs three lines and it is the difference between a program that tells you the disk is full and one that reports success while producing nothing. If you expect to be asked about this code in an interview, being able to say plainly why the check matters is exactly the kind of reasoning covered in explaining your coding solution in an interview. And if your next task involves matching file names or header keys, read comparing two strings while ignoring case in C++, where the naive version has a genuine undefined-behavior bug.

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Telegram Email Copy Link
    Previous ArticleHow to Install WordPress on a Local Host (2026 Guide)
    Next Article How to Watch Australia vs. British & Irish Lions From Anywhere: Stream 3rd Test Rugby Union Free
    Ethan Caldwell

      Ethan Caldwell is GeekBlog's resident Apple specialist, covering the entire Apple ecosystem - iPhone, iPad, Mac, Apple Watch, AirPods and the software that ties them together. A longtime iOS user and gadget collector, Ethan tracks Cupertino's every move, breaking down Apple keynotes, A- and M-series chip benchmarks, iOS feature updates and the rumor mill into clear, practical takes that help readers decide whether the latest Apple hardware is worth the upgrade.

      Related Posts

      22 Mins Read

      How to Overload an Operator in C++ (Complete Guide)

      16 Mins Read

      How to Use Average True Range (ATR): Beginner’s Guide

      17 Mins Read

      How to Interpret Candlestick Patterns (Not Just Name Them)

      11 Mins Read

      How to Turn Off Facebook Notifications (Every Type)

      11 Mins Read

      How to Hide Your Friends List on Facebook (2026)

      15 Mins Read

      The Basics of Candlestick Patterns: A Beginner’s Guide

      Top Posts

      Japan Skips Exams Until Age 10 and Teaches Character Instead. The Results Are Complicated.

      July 29, 20268 Views

      Husbands Cause More Stress Than Kids? Science Says Many Moms Feel Exactly That

      July 28, 20268 Views

      How to Block Twitch Ads with uBlock Origin (2026 Guide)

      June 15, 20267 Views
      Stay In Touch
      • Facebook

      Subscribe to Updates

      Get the latest tech news from FooBar about tech, design and biz.

      Most Popular

      Best Stores for Buying MP3 and Digital Music You Can Keep Forever (2026)

      August 2, 2025901 Views

      Discord will require a face scan or ID for full access next month

      February 9, 2026770 Views

      Trade in your old phone and get up to $1,100 off a new iPhone 17 at AT&T – here’s how

      September 10, 2025382 Views
      Our Picks

      How to Overload an Operator in C++ (Complete Guide)

      July 30, 2026

      How to Use Average True Range (ATR): Beginner’s Guide

      July 30, 2026

      How to Interpret Candlestick Patterns (Not Just Name Them)

      July 30, 2026

      Subscribe to Updates

      Get the latest creative news from FooBar about art, design and business.

      Facebook
      • About Us
      • Contact us
      • Privacy Policy
      • Disclaimer
      • Terms and Conditions
      © 2026 GeekBlog

      Type above and press Enter to search. Press Esc to cancel.