How it works...

In this application, we use the same idea  (static arrays of preallocated objects) that we used in the first recipe; however, we wrap it into a templated ObjectPool class to provide a generic interface for handling objects of different types.

Our template has two parameters—a class or a data type of objects stored in an instance of the ObjectPool class, and the pool size. These parameters are used to define two private data fields of the class—an array of objects and an array of free indices:

     T objects[N];
size_t available[N];

Since template parameters are being resolved at compile time, these arrays are allocated statically. Additionally, the class has a private data member called top that acts as an index in the available array and points to the next available object.

The available array contains indices of all objects in the objects array that are currently available for use. At the very beginning, all objects are free, and the available array is populated with indices of all elements in the objects array: 

      for (size_t i = 0; i < N; i++) {
available[i] = i;
}

When the application needs to get an element from the pool, it invokes the get method. This method uses the top variable to get the index of the next available element in the pool:

      size_t idx = available[top++];
return objects[idx];

When the top index reaches the size of the array, it means that no more elements can be allocated, and so the method throws an exception to indicate the error condition: 

      throw std::runtime_error("All objects are in use");

Objects can be returned into the pool using  free . First, it detects an index of the element based on its address. The index is calculated as a difference between the object address and the pool start address. Since  pool objects are stored in memory contiguously, we can easily filter out objects of the same type, but not those that originate from this pool:

      const T* ptr = &obj;
size_t idx = (ptr - objects) / sizeof(T);

Note that, since the size_t type is unsigned, we do not need to check that the resulting index is less than zero—it is not possible. If we try to return an object to the pool that does not belong to it and has an address less than the pool's start address, it will be treated as a positive index anyway.

If the object we return belongs to the pool, we update the top counter and put the resulting index into the available array for further use:

  top--;
available[top] = idx;

Otherwise, we throw an exception indicating that we tried to return an object that was not taken from this pool:

     throw std::runtime_error("Freeing object that does not belong to the pool");

The method requested is used to track pool object usage. It returns the top variable, which efficiently tracks the number of objects that were claimed but have not yet been returned to the pool.

     size_t requested() const { return top; }

Let's define a data type and try to work with objects from the pool. We declare a struct called Point that holds two int fields, as shown in the following code:

 struct Point {
int x, y;
};

Now we create a pool of Point objects of size 10:

    ObjectPool<Point, 10> points;

We get one object from the pool and populate its data fields:

 Point& a = points.get();
a.x = 10; a.y=20;

The program produces the following output:

The first line of the output reports one object as requested.

We request one more object and print its data fields as-is, without any initialization. The pool reports that two objects were requested, as expected.

Now we return our first object back to the pool and make sure that the count of requested objects decreases. We can also note that, even after returning the object to the pool, we can read data from it.

Let's claim one more object from the pool. The requested count increases, but the requested object is the same as the one we returned on the preceding step.

We can see that Point c was not initialized after it was taken from the pool, but its fields contain the same values as Point a. In fact, now a and c are references to the same object in the pool, and so the modification of variable a will affect variable c. This is one of the limitations of our implementation of the object pool.

Finally, we create a local Point object and try to return it into the pool:

  Point local;
try {
points.free(local);
} catch (std::runtime_error e) {
std::cout << "Exception caught: " << e.what() << std::endl;
}

It is expected to fail with an exception, and it does. In the program output, you can see an Exception caught: Freeing object that does not belong to the pool message.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.107.90