RAII With Acquisition Error Handling

Let’s return a `std::unique_ptr` from a static constructor. Internally, this will create a `Logger` object on the heap, wrap it in a `unique_ptr`, and return it to the client. If we fail to open the log file, we will return a `nullptr` instead. This way, we will inform the client that we failed when we actually failed.

Let’s see how we can use this API:

C++




class Logger {
public:
    static std::unique_ptr<Logger>
    Create(const std::string& log_file_path)
    {
        int32_t log_fd = open(log_file_path, O_RDWR);
        if log_fd
            < 0 { return nullptr; }
 
        return std::make_unique<Logger>(log_fd);
    }
 
    void Log(const std::string& event)
    {
        // Write the event minus the null terminator.
  if ((write(log_fd_, event.c_str(), event.length()) != event.length) {
            std::cerr << "Failed to log event: " << event;
            return;
     }
    }
 
    ~Logger()
    {
        // Clean up our resources before the object goes
        // away.
        close(log_fd_);
    }
 
private:
    explict Logger(int32_t log_fd)
        : log_fd_(log_fd)
    {
    }
    int32_t log_fd_;
}


Now that the client knows when a `Logger` creation fails, it can take necessary steps without being confused by `Log` API failures. When `logger` goes out of scope, its destructor is called, and the log file is closed.

Resource Acquisition Is Initialization

RAII stands for “Resource Acquisition Is Initialization”. Suppose there is a  “resource” in terms of Files, Memory, Sockets, etc. RAII means that an object’s creation and destruction are tied to a resource being acquired and released.

Let’s assume we have to write events to a log file. A non-object-oriented way to do this would be:

C++




void WriteToLog(int32_t log_fd, const std::string& event)
{
    // use 'write' syscall to write to the log.
}
 
int main(int argc, char** argv)
{
    int32_t log_fd = open("/tmp/log", O_RDWR);
 
    if (log_fd < 0) {
        std::cerr << "Failed to open log file";
        return -1;
    }
 
    WriteToLog("Event1");
    WriteToLog("Event2");
 
    // What are we missing here ?
    return 0;
}


We forgot to close log_fd. It’s imperative that every Linux process closes its file descriptors during its lifetime; otherwise, it may run out of file descriptors and misbehave/fail. Additionally, leaving file descriptors open will cause the kernel to keep extra bookkeeping and state around for unused yet open file descriptors and their backing file objects, resulting in increased memory consumption and unnecessary work.

Similar Reads

An Object-Oriented Approach

...

RAII To The Rescue

Consider a Logger class that writes events to a log file. In order to write to the file, it will need to open a file descriptor to it. Let’s take a look at how a primitive version of this class might look:...

RAII With Acquisition Error Handling

...

Other Scenarios where RAII can be used

...