Skip to content

Standard Template Library usage

Modern C++ uses the Standard Template Library (STL) which is a library that comprises a lot of algorithms as well as standard containers, most of which have been optimised in the best possible way (though some of them still cause problems). Using these containers and algorithms is good practice.

In this code, we could have used C-style arrays and raw pointers. This could be harmless in the case that the code base stays the same and the developers can be sure about their memory management and ownership semantics. But, as the code base expands, it would have been more difficult to track every object, moving from function to function. Instead, to comply with modern C++ practices, we have used std::vector instead of dynamically allocated arrays and std::unique_ptr instead of raw pointers.

std::vector

The vector container behind the scenes is simply an array, minus the painful memory management that would be needed if we had used raw arrays. Using vectors can create an overhead since the developer can expand the container by using, for example, the push_back method, but there is a catch. The std::vector allocates a specified space in memory at initialization. If the developer "pushes" more objects in it than memory exists, it would need to allocate another space - larger this time - and copy all the elements from one container to the other. As one can imagine, this can create a serious overhead in very large vectors. However, the authors of STL have thought about this case and they have included a pretty handy method called reserve. With reserve, the developer can allocate exactly how much memory the object will need, given that the size is a known quantity. This could relax the constraints about performance hits.

Another useful thing about vectors is that they use contiguous memory, meaning each element should be "side to side" inside memory. That means that a simple access should give an average of in terms of time. Since the code base includes a lot of vector access operations, we assessed that this container is the best option for our cases.

std::unique_ptr

Smart pointers were an addition to the STL in order to avoid the need for manual memory allocation for an object and then (and most crucially) manual deallocation of its resources. Smart pointers are part of a C++ programming technique called Resource Acquisition Is Initialization (RAII) which binds the lifetime of a resource that must be acquired before use (like memory on the heap) to the lifetime of an object. What this means is that the smart pointer is only valid once it's in scope. Once it goes out of scope, all of its resources are properly freed. This way the developer can be sure about proper memory management. There are several smart pointers (namely std::shared_ptr, std::unique_ptr and std::weak_ptr) but here we will discuss std::unique_ptr since this is the one used in our code base and is by far the most commonly used elsewhere.

We used std::unique_ptr wherever there was a need for a pointer in the code base. There's nothing wrong with using a raw pointer, but raw pointers shouldn't handle ownership, meaning a raw pointer is good enough only when it doesn't own anything (i.e passing it to a function which does some calculations).

One thing that stands out for std::unique_ptr is that it cannot be copied. That means that the ownership of the managed resource needs to be transferred to a new object if the developer wants to move the pointer. The original object will no longer own the resource and the original std::unique_ptr will contain a nullptr. To create a std::unique_ptr it is good practice to use std::make_unique as can be seen in the snippet below:

auto fluidPtr = std::make_unique<Fluid>(nbParticles);

After this the user can use fluidPtr as they would use a raw pointer, with the difference being that they don't (and shouldn't) delete the pointer at the end of the scope.