Is there any RAII file handle already implemented?
Asked Answered
A

3

12

A RAII file handle looks pretty basic so I guess it has already been implemented? But I couldn't find any implementation. I found file_descriptor in boost::iostreams but I don't know if it's what I'm looking for.

Asthenia answered 1/4, 2014 at 2:4 Comment(6)
File handles are too old-style C to attract attention from the C++ gurus. I'm with you though.Goodnatured
You couldn't find std::fstream?Beardsley
@MarkRansom file handles are OS primitives, not "old-style C" things.Fiddlestick
@DevNull to me a "file handle" means FILE*. What does it mean to you?Goodnatured
@MarkRansom it can also mean file descriptor, as in man7.org/linux/man-pages/man2/open.2.html, or HANDLE, as in msdn.microsoft.com/en-us/library/windows/desktop/…Fiddlestick
@MarkRansom That means a stdio file . A file descriptor is an FD, a small integer.Wolfe
M
9

std::fstreams support RAII-style usage - they can be opened and even tested at construction, and they're automatically flushed and closed in the destructor, though you could miss errors if you just assume that works so you may want to do something more explicit in code if you need the robustness.

For example:

if (std::ifstream input(filename))
    ... use input...
else
    std::cerr << "unable to open '" << filename << "'\n";

If you really want to use file descriptors, you can tune something like the following to taste. It's a bit longer than something that just invokes close, but if you want to do robust programming you need to check for and handle errors somehow....

#include <iostream>
#include <iomanip>
#include <string>
#include <exception>
#include <sstream>
#include <cstring>  // for strerror
#include <unistd.h>  // for close

struct Descriptor
{
    Descriptor(const Descriptor&) = delete;
    Descriptor(Descriptor&& other) noexcept
      : fd_{other.fd_}, filename_{std::move(other.filename_)}
    { other.fd_ = -1; }

    Descriptor(int fd, std::string filename = "")
      : fd_{fd}, filename_{std::move(filename)}
    {
        if (fd < 0)
        {
            std::ostringstream oss;
            throw std::runtime_error(err(oss, "open").str());
        }
    }
    ~Descriptor()
    {
        if (fd_ != -1 && close(fd_) == -1)
            // destructor throw risks termination; avoid
            err(std::cerr, "close") << '\n';
    }
    void operator=(const Descriptor&) = delete;
    Descriptor& operator=(Descriptor&& rhs) noexcept {
        swap(rhs);
        return *this;
    }
    void swap(Descriptor& other) noexcept {
        std::swap(fd_, rhs.fd_);
        std::swap(filename_, rhs.filename_);
    }     
    operator int() const { return fd_; }

  private:
    int fd_;
    std::string filename_;
    
    auto err(auto& os, const char* operation) const -> decltype(os) {
        os << "failed to " << operation << " file";
        if (!filename_.empty())
            os << ' ' << std::quoted(filename_);
        os << ": " << strerror(errno);
        return os;
    }
};


#include <fcntl.h>
int main()
try
{
    const char* filename = "/proc/cmdline";
    Descriptor fd(open(filename, O_RDONLY), filename);
    char buffer[1024];
    std::cout.write(buffer, read(fd, buffer, sizeof buffer)); // TODO error handling
}
catch (const std::exception& e)
{
    std::cerr << e.what() << '\n';
}
Mismatch answered 1/4, 2014 at 2:9 Comment(5)
is Descriptor class missing const char* filename_ field?Kostroma
@FrancescoRogo yes, or perhaps a std::string so there's no risk of it ending up pointing somewhere invalid (though that will use more memory), and it should really have a move constructor and assignment and the copy versions deleted....Mismatch
That struct Descriptor is broken. You defaulted the move constructor, but it doesn't do the right thing. At a minimum, you should remove that and delete the copy assignment, if you don't want to implement correct move semantics.Transliterate
It would copy fd_, then close it twice when the two instances of Descriptor are destroyed.Transliterate
@HolyBlackCat: ah jeez... quite so. I'll fix. ThanksMismatch
I
8

Depends on what exactly you want.

If you really want a scoped handle, use:

std::unique_ptr<HANDLETYPE, closehandletypefunction> smartpointer;

For FILE pointers, this would look like

std::unique_ptr<FILE, int (*)(FILE *)> f(fopen("myfile.txt", "a"), fclose);

The FILE* can then be gotten with f.get(). The same thing would work with file descriptors (open and close from <fcntl.h> and <unistd.h>, respectively).

The preferred C++ way is wrapping the handle in an object with thousands of members to do everything though.

Isostasy answered 1/4, 2014 at 2:12 Comment(2)
How does the same thing work with with file descriptors? The ints, not pointers, so how would I do this?Nilson
@Nilson you can't with unique_ptr. See #15757460Waldack
A
4

I am using boost::filesystem::ifstream (or ofstream for writing).

I was actually asking this because I wanted to be sure that my file was closed even if an exception was raised before calling file.close()

But after reading the documentation again:

In case that an object is destroyed while still associated with an open file, the destructor automatically calls the member function close.

So, this is safe :)

Asthenia answered 1/4, 2014 at 4:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.