Builder for C++ Resource Class

This is a useful pattern people should use in cpp

One problem with cpp classes is the method for communicating failures in a constructor. Imagine a class that acquire a resource. Such as:

  • creating a file
  • handle a network connection
  • allocating dynamic memory from the heap

What if the acquisition fails? Will the user know?

An acquisition internal to the constructor failing would leave the object in a incomplete state. The user would be non the wiser. We can just throw an exception from the constructor. It would and can work, but there is something wrong about it. Constructors return an instance of the class they construct. If it also throws an exception it does more than returning an instance of the class. It also throws exceptions. That does not sit right with me. Exceptions are not the answer.

Illustration of constructor throwing exceptions

Anther way to communication errors or acquire resources in a cpp class is to use an initialize function. The expected use is: declare the object, call initialize, then use the object. Calling the functions in any other order will not work. How can the user know the right order? This pattern is really easy to improperly use. When using an object it should be usable instantly after initialization. Using a initialization function does not solve the issue.

Illustration of constructor then Init

There is an option and We can simply use the builder pattern to communicate the errors during construction.using the pattern is simple. Dead simple, (but this is C++ so its complicated)

Illustration of Builder

Essentially its two parts

  • Builder returns an optional<“our class”>
  • Constructor is kept private

with our builder, returning a optional<“our class”> we either get a fully baked object or a nullopt. This is how the error is communicated. unlike the initialization function pattern, there is only one way to use this pattern. This helps the user use the class. With the builder, you are either returning the object correctly. Otherwise, you return essentially nothing. The user can check the return value.

In this example we will simulate acquiring a resource that can fail. The acquire function returns a random number between 0-200. If the random number generated is above than 100 it is a failure otherwise success.

bool aquire(int &randomResource) {
  std::random_device rd;
  std::mt19937 gen(rd());
  std::uniform_int_distribution<int> dist(0, 200);
  int generated = dist(gen);
  bool success = false;
  if (generated <= 100) {
    randomResource = generated;
    success = true;
  }
  return success;
}

Part 1 the builder

In the class we then declare the builder our builder is called “make resource.” There is really only one rule to adding a builder to a cpp class. that is it needs to be static. this rule comes with a side effect, and a side effect rule. Side effect: any function that is called in the builder needs to be static. Side effect rule: do not use a static variable inside your builder.

class resource {
public:
  static std::optional<resource> makeResource() {
    std::optional<resource> res = std::nullopt;
    int i = -1;
    if (aquire(i)) {
      std::cout << "Acquiring: item: " << i << std::endl;
      res = std::make_optional<resource>({i});
    } else {
      std::cout << "Acquisition failed" << std::endl;
    }
    return res;
  };
...

Part 2 make the constructor private

private:
  resource(int i) : item(i) {
    std::cout << "D(i) item: " << item << std::endl;
  };
  int item{-1};
};
...

Beyond that we just need to add in the typical class structure. I implemented it as a move only class.

  resource &operator=(resource &&rhs) noexcept {
    std::cout << "= Item: this: " << this->item << " rhs: " << rhs.item
              << std::endl;
    if (&rhs != this) {
      std::swap(this->item, rhs.item);
    }
    return *this;
  };
  resource(resource &&rhs) noexcept {
    std::cout << "Copy() Item: this: " << this->item << " rhs: " << rhs.item
              << std::endl;
    if (&rhs != this) {
      std::swap(this->item, rhs.item);
    }
  };
  ~resource() {
    if (item != -1) {
      std::cout << "destroying: " << item << std::endl;
    }
  };
  resource &operator=(resource &rhs) = delete;
  resource(resource &rhs) = delete;
  resource() = delete;
...

The little test program

resource fnRetResource() { return resource::makeResource().value(); }
int main() {
  auto r = fnRetResource();
  return 0;
};

The test program running

Leave a Reply

Discover more from Silly Code Projects

Subscribe now to keep reading and get access to the full archive.

Continue reading