An Object-Oriented Approach

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:

C++




class Logger {
public:
    Logger();
 
    bool Initialize(const std::string& log_file_path)
    {
        log_fd_ = open(log_file_path, O_RDWR);
        return (log_fd_ < 0 ? false : true);
    }
 
    void Log(const std::string& event);
 
    ~Logger()
    {
        // Clean up our resources before the object goes
        // away.
        close(log_fd_);
    }
 
private:
    int32_t log_fd_;
}


Now let’s see how we will use this API:

C++




int main(int argc, char** argv)
{
    Logger logger = Logger();
    if (!logger.Initialize("/tmp/log")) {
        std::cerr << "Failed to initialize logger";
        return -1;
    }
    logger.Log("some random event");
    return 0;
}


The good thing is that upon destruction, the `Logger` closes the underlying file descriptor, making it responsible for its own resources.

However, the `logger` object is in limbo between the `Constructor` and the `Initialize` call, as it has no file to write to internally. Relying on “good and responsible” users to use our API judiciously is always a bad code smell.

Let’s see if we can do better:

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

...