c++17fstreamiostreamofstream

What is the C++ iostream to creating a file with O_EXCL?


I want to create an output file in a threadsafe manner, and only if it does not exist. I want to use the file system for synchronization. With open() I would use the flags O_RWRONLY|O_CREAT|O_EXCL. Is there a way to do this in C++17 using the iostream or fstream ?


Solution

  • Prior to C++23 there is no way of opening an ofstream in exclusive mode.

    Workaround: Use std::fopen which has this capability since C++17.

    Example:

    #include <cstdio>
    
    // Mode "x" to make it fail if it already exists
    std::FILE* fp = std::fopen("filename", "wx");
    
    if(fp) {
        // created exclusively
    
        // work with fp ...
    
        std::fclose(fp);
    }
    

    If you really want an ofstream you could create a helper function:

    template<class Stream>
    Stream open_exclusively(const std::string& filename) {
        Stream rv;
        
        if(std::FILE* fp = std::fopen(filename.c_str(), "wx"); fp) {
            std::fclose(fp);
    
            // overwrite the file that was created exclusively:
            rv.open(filename);
        } else {
            // could not create file exclusivly, set the failbit in the stream:
            rv.setstate(Stream::failbit);
        }
    
        return rv;
    }
    
    int main() {
        auto os = open_exclusively<std::ofstream>("filename");
    
        if(os) {
            std::cout << "file created exclusively\n";
        }
    }
    

    Demo

    Edit:

    Even though the above demo is compliant and works on all platforms I've tested it - wine (v6.16) can't handle it, so I opened a bug report at bugs.winehq.org. You can follow the progress here:

    Standard library call fopen(..., "wx") not recognized - causes destruction of data

    Edit 2:

    The Wine bugfix ucrtbase: Add support for x mode in fopen is now included in Wine 6.20 so after upgrading to 6.20 (or later), this will be working as it should in Wine too.


    From C++23 you can use the std::ios::noreplace openmode:

    std::ofstream os("filename", std::ios::noreplace);
    
    if(os) {
        std::cout << "file created exclusively\n";
    }