RR: Avoiding Memory Allocation
The C++ language has a range of excellent tools for managing allocation and de-allocation of memory. They are an essential part of writing effective and efficient programs. The first strategy, however, for dealing with memory allocation should be to avoid it. If that is not possible, you should try to separate the logic of what you are trying to achieve from the memory allocation routines.
Example
As usual, it is easiest to criticise an example:
// Bojan Nikolic <bojan@bnikolic.co.uk>
//
// An examle of how not the generate random numbers
#include <boost/random.hpp>
#include <iostream>
double * getRand(size_t n)
{
boost::mt19937 rng(43);
static boost::uniform_01<boost::mt19937> zeroone(rng);
double *res= new double[n];
for(size_t i=0; i<n; ++i)
{
res[i]=zeroone();
}
return res;
}
int main(void)
{
const size_t n=10;
double *r=getRand(n);
for(size_t i=0; i<n; ++i)
{
std::cout<<r[i]<<" ";
}
}
What are the problems with the function getRand()?
- It returns a pointer to newly allocated memory that the caller must know to de-allocate with delete[] (and the user has forgotten to do that in the example above)
- It forces to user to allocate memory even if she wants only one random number
- It provides no flexibility in how memory is allocated, e.g., maybe the user wants to use a memory pool or similar strategy
As you can see, all of the above are related to the basic issue that the function is trying to do two things at once:
- Generating random numbers
- Allocating memory for numbers
Like it is emphasised in basic programming courses, trying to do two things in one function is a bad idea and this is no exception.
Improved example
Here is an improved example:
// Bojan Nikolic <bojan@bnikolic.co.uk>
//
// Improved example
#include <boost/random.hpp>
#include <iostream>
void getRand(double *res,
size_t n)
{
boost::mt19937 rng(43);
static boost::uniform_01<boost::mt19937> zeroone(rng);
for(size_t i=0; i<n; ++i)
{
res[i]=zeroone();
}
}
int main(void)
{
const size_t n=10;
double * r = new double[n];
getRand(r,n);
for(size_t i=0; i<n; ++i)
{
std::cout<<r[i]<<" ";
}
}
As you can see the signature of the function has changed to:
void getRand(double *res,
size_t n);
In other words, the user supplies the space and the functions concentrates only on generating the random number. Therefore it is possible to efficiently generate even single random numbers, to continuously reuse the same space, etc.
Next problem
We are still allocating memory using clunky double * r = new double[n]; and as eventually always happens with such allocations, forgetting to de-allocate. In next article, I will provide suggestions on how to avoid that.
Summary
Although I've used a concrete example above, there is a useful general tip to maybe take away:
If you can in advance determine the memory required by a function, allow the user an option of supplying that memory rather than allocating it within the function.
The advantage of this approach is that:
- It can be faster
- It avoids possible memory fragmentation
- It separates code into smaller more manageable units
- It reduces the probability of errors in the program