views:

190

answers:

5

I am doing a project converting some Pascal (Delphi) code to C++ and would like to write a function that is roughly equivalent to the Pascal "SetLength" method. This takes a reference to a dynamic array, as well as a length and allocates the memory and returns the reference.

In C++ I was thinking of something along the lines of

void* setlength(void* pp, int array_size, int pointer_size, int target_size, ....) {
      void * p;
      // Code to allocate memory here via malloc/new
      // something like:   p = reinterpret_cast<typeid(pp)>(p); 
      //                   p=(target_size) malloc(array_size);
      return p;
}

My question is this: is there a way to pass the pointer type to a function like this and to successfully allocate the memory (perhaps via a typeid parameter?)? Can I use

<reinterpret_cast>

somehow? The ultimate aim would be something like the following in terms of usage:

float*** p;
p=setlength(100,sizeof(float***),sizeof(float**),.....);

class B;
B** cp;
cp=setlength(100,sizeof(B**),sizeof(B*),.....);

Any help would be most welcome. I am aware my suggested code is all wrong, but wanted to convey the general idea. Thanks.

+1  A: 

For a multidimensional array, probably the best option would be to use boost's multi_array library:

typedef boost::multi_array<float, 3> array_type;
array_type p(boost::extents[100][100][100]); // make an 100x100x100 array of floats
p[1][2][3] = 4.2;

This lets you completely abstract away the allocation and details of setting up the multidimensional array. Plus, because it uses linear storage, you get the efficiency benefits of linear storage with the ease of access of indirections.

Failing that, you have three other major options.

The most C++-y option without using external libraries would be to use a STL container:

std::vector<float **> p;
p.resize(100);

As with multi_array, p will then automatically be freed when it goes out of scope. You can get the vector bounds with p.size(). However the vector will only handle one dimension for you, so you'll end up doing nested vectors (ick!).

You can also use new directly:

float ***p = new float**[100];

To deallocate:

delete [] p;

This has all the disadvantages of std::vector, plus it won't free it for you, and you can't get the size later.

The above three methods will all throw an exception of type std::bad_alloc if they fail to allocate enough memory.

Finally, for completeness, there's the C route, with calloc():

float ***p = (float ***)calloc(100, sizeof(*p));

To free:

free((void*)p);

This comes from C and is a bit uglier with all the casts. For C++ classes it will not call the constructors for you, either. Also, there's no checking that the sizeof in the argument is consistent with the cast.

If calloc() fails to allocate memory it will return NULL; you'll need to check for this and handle it.

bdonlan
I certainly wouldn't call them monstrosities, they give a much more intuitive way of accessing multi-dimensional data structures. That being said you can lose some performance with comparison to linear array accessed major dimension first.
DeusAduro
This is what I am using at the moment, but wondered if there was a way to put all the exception checking in one place on allocation. My application uses lots of 3D arrays where triple indirection is (alas) necessary
Ian Buss
much more intuitive than what?
jalf
Note that triple indirection isn't needed for 3 or higher dimension arrays. You can have a linear array of length equal to the product of all dimensions.
DeusAduro
@Jalf more intuitive to the linear array, which represents more than one dimension. array[i][j] is easier and simpler than array[i*dim2+j], not to mention making sure you access row/column major appropriately.
DeusAduro
Added some info on boost's multi_array, which might be what you want for 3D arrays.
bdonlan
For the record, I've had some performance issues with boost::multi_array: It works nicely, but performance isn't anywhere near that of native arrays (or wasn't when I tried it)
jalf
+5  A: 

Use std::vector instead of raw arrays. Then you can simply call its resize() member method.

And make the function a template to handle arbitrary types:

If you want to use your function, it could look something like this:

template <typename T>
std::vector<T>& setlength(std::vector<T>& v, int new_size) {
      v.resize(new_size);
      return v;
}

But now it's so simple you might want to eliminate the function entirely and just call resize to begin with.

I'm not entirely sure what you're trying to do with the triple-pointers in your example, but it looks like you don't want to resize though, you want to initialize to a certain size, which can be done with the vector constructor:

std::vector<float>v(100);
jalf
A: 

The new operator itself is essentially what you are asking for, with the exception that to appropriately allocate for double/triple pointers you must do something along the following lines:

float** data = new float*[size_of_dimension_1];
for ( size_t i=0 ; i<size_of_dimension_1 ; ++i )
    data[i] = new float[size_of_dimension_2];

...

// to delete:
for ( size_t i=0 ; i<size_of_dimension_1 ; ++i )
    delete [] data[i];
delete [] data;

Edit: I would suggest using one of the many C++ math/matrix libraries out there. I would suggest uBlas.

DeusAduro
Yes, this is how I have been doing them, but for every type a new loop must be written (i.e. one for a 3D array of floats, one for a 3D array of class objects...). I am somewhat hamstrung by the fact that I am doing a translation job, rather than from scratch.
Ian Buss
+2  A: 

If you wanted to do it literally, you would do it like this:

template <typename T>
T* SetLength(T* arr, size_t len) {
    return static_cast<T*>(realloc(arr, sizeof(T) * len));
}

Note that the array must have been allocated with malloc or calloc. Also note that this does not actually resize the memory—it deallocates the memory and reallocates memory of the appropriate size. If there were any other pointers to the array being passed in, they will be invalid afterwards.

You're really better off using a more idiomatic C++ solution, like std::vector.

John Calsbeek
A: 

To do this the C++ way:

1) As jalf stated, prefer std::vector if you can
2) Don't do void* p. Prefer instead to make your function a template of type T.

Shaun