Resource Management

A good resource management will achieve #202202012306.

Always perfer Rule of Zero for business-logic classes or when there is no need for direct resource management. When resource management is inevitable, follow Rule of Three, Rule of Five or Rule of Four.

Rule of Zero

In Rule of Zero, there is no need to manually initialise anything. We could just use the default destructor, copy constructor and copy assignment.

class Resource {
  // ...

public:
  Resource(const Resource&) = default;
  Resource& operator=(const Resource&) = default;
  ~Resource() = default;
}

Rule of Three

In Rule of Three, we need to pay attention to three components: destructor, copy constructor and copy assignment.

Consider the following class:

class Resource {
  int *ptr_;
  size_t size_;

public:
  Resource() : ptr_(nullpr), size_(0) {}
}

Destructor is used to clean-up resources that is about to be out of scope in order to prevent memory leak. This is especially useful in #exception handling as it inevitably meddles with the resource management if destructor is not used. In this example, we are going to destroy the integer pointers.

  ~Resource() { delete [] ptr_; }

However, in some special cases when the resources are copied, double frees could be happened. Copy constructor is used to handle this situation.

  Resource(const Resource& rhs) {
    ptr_ = new int[rhs.size_];
    size_ = rhs.size_;
    std::copy(rhs.ptr_, rhs.ptr_ + size_, ptr_);
  }

However, there is an edge case that double frees could still happen. We need copy assignment operator instead. Before that, we have to understand the difference between initialisation and assignment.

Resource w = v; // initialisation: call copy constructor

Resource w;
w = v;          // assignment: call copy assignment operator

Applying copy and swap idiom for copy assignment operator as below:

  Resource& operator=(const Resource& rhs) {
    Resource copy = rhs;
    copy.swap(*this);
    return *this;
  }

Where swap could be defined like this:

  friend void swap(Resource& a, Resource& b) {
    a.swap(b);
  }

  void swap(Resource& rhs) {
    using std::swap;
    swap(ptr_, rhs.ptr_);
    swap(size_, rhs.size_);
  }

We could explicitly state that this class is non-copyable by using delete keyword.

  Resource(const Resource&) = delete;
  Resource& operator=(const Resource&) = delete;

Rule of Five

Rule of Five is an addition to Rule of Three for correctness and performance. It adds two move rules: move constructor and move assignment operator, which are aim at rvalues.

For move constructor:

  Resource(Resource&& rhs) {
    ptr_ = std::exchange(rhs.ptr_, nullptr);
    size_ = std::exchange(rhs.size_, 0);
  }

And for move assignment operator using copy-swap idiom:

  Resource& operator=(const Resource&& rhs) {
    Resource copy(std::move(rhs));
    copy.swap(*this);
    return *this;
  }

Rule of Four

Rule of Four is an alternative version of Rule of Five with the exclusion of move assignment operator. Instead, it uses a by-value assignment operator which do copy and move.

  Resource& operator=(Resource rhs) {
    copy.swap(*this);
    return *this;
  }
Links to this page
#cpp #resource #exception