RAII To The Rescue

Putting RAII into practice, let’s acquire the file in the `Logger` constructor itself. Here’s how it would look:

C++




class Logger {
public:
    explicit Logger(const std::string& log_file_path)
    {
        log_fd_ = open(log_file_path, O_RDWR);
    }
 
    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:
    int32_t log_fd_;
}


 

This version improves our previous API by acquiring the file in the constructor. It still has one flaw but let’s see how we use this API:

C++




int main(int argc, char** argv)
{
    Logger logger = Logger("/tmp/log");
 
    // What happens here if log_fd_ < 0 in the Constructor.
    logger.Log("some random event");
 
    return 0;
}


If we fail to open the log file, then all logging events will fail. We could have `Log` return an error, but the client may keep retrying. To the client, the object creation succeeded, implying that the `Logger` is ready to log events. Any `Log` errors may be transient and worth retrying.

However, this is not the case. All `Log` calls will fail. We should have returned an error where it happened – in the constructor. Constructors, unfortunately, cannot return errors. Let’s see an elegant way to get around this.

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

...