Pass nested C++ vector as built-in style multi-dim

2019-07-13 00:23发布

问题:

If I have a vector in C++, I know I can safely pass it as an array (pointer to the contained type):

void some_function(size_t size, int array[])
{
    // impl here...
}

// ...
std::vector<int> test;
some_function(test.size(), &test[0]);

Is it safe to do this with a nested vector?

void some_function(size_t x, size_t y, size_t z, int* multi_dimensional_array)
{
    // impl here...
}

// ...

std::vector<std::vector<std::vector<int> > > test;
// initialize with non-jagged dimensions, ensure they're not empty, then...
some_function(test.size(), test[0].size(), test[0][0].size(), &test[0][0][0]);

Edit:

If it is not safe, what are some alternatives, both if I can change the signature of some_function, and if I can't?

回答1:

Short answer is "no".

Elements here std::vector<std::vector<std::vector<int> > > test; are not replaced in contiguous memory area.



回答2:

You can only expect multi_dimensional_array to point to a contiguos memory block of size test[0][0].size() * sizeof(int). But that is probably not what you want.



回答3:

It is erroneous to take the address of any location in a vector and pass it. It might seem to work, but don't count on it.

The reason why is closely tied to why a vector is a vector, and not an array. We want a vector to grow dynamically, unlike an array. We want insertions into a vector be a constant cost and not depend on the size of the vector, like an array until you hit the allocated size of the array.

So how does the magic work? When there is no more internal space to add a next element to the vector, a new space is allocated twice the size of the old. The old space is copied to the new and the old space is no longer needed, or valid, which makes dangling any pointer to the old space. Twice the space is allocated so the average cost of insertion to the vector that is constant.



回答4:

Is it safe to do this with a nested vector?

Yes, IF you want to access the inner-most vector only, and as long you know the number of elements it contains, and you don't try accessing more than that.

But seeing your function signature, it seems that you want to acess all three dimensions, in that case, no, that isn't valid.

The alternative is that you can call the function some_function(size_t size, int array[]) for each inner-most vector (if that solves your problem); and for that you can do this trick (or something similar):

void some_function(std::vector<int> & v1int)
{
    //the final call to some_function(size_t size, int array[]) 
    //which actually process the inner-most vectors
    some_function(v1int.size(), &v1int[0]);
}
void some_function(std::vector<std::vector<int> > & v2int)
{
    //call some_function(std::vector<int> & v1int) for each element!
    std::for_each(v2int.begin(), v2int.end(), some_function);
}

//call some_function(std::vector<std::vector<int> > & v2int) for each element!
std::for_each(test.begin(), test.end(), some_function);


回答5:

A very simple solution would be to simply copy the contents of the nested vector into one vector and pass it to that function. But this depends on how much overhead you are willing to take.

That being sad: Nested vectorS aren't good practice. A matrix class storing everything in contiguous memory and managing access is really more efficient and less ugly and would possibly allow something like T* matrix::get_raw() but the ordering of the contents would still be an implementation detail.



回答6:

Simple answer - no, it is not. Did you try compiling this? And why not just pass the whole 3D vector as a reference? If you are trying to access old C code in this manner, then you cannot.



回答7:

It would be much safer to pass the vector, or a reference to it:

void some_function(std::vector<std::vector<std::vector<int>>> & vector);

You can then get the size and items within the function, leaving less risk for mistakes. You can copy the vector or pass a pointer/reference, depending on expected size and use.

If you need to pass across modules, then it becomes slightly more complicated.



回答8:

Trying to use &top_level_vector[0] and pass that to a C-style function that expects an int* isn't safe.

To support correct C-style access to a multi-dimensional array, all the bytes of all the hierarchy of arrays would have to be contiguous. In a c++ std::vector, this is true for the items contained by a vector, but not for the vector itself. If you try to take the address of the top-level vector, ala &top_level_vector[0], you're going to get an array of vectors, not an array of int.

The vector structure isn't simply an array of the contained type. It is implemented as a structure containing a pointer, as well as size and capacity book-keeping data. Therefore the question's std::vector<std::vector<std::vector<int> > > is more or less a hierarchical tree of structures, stitched together with pointers. Only the final leaf nodes in that tree are blocks of contiguous int values. And each of those blocks of memory are not necessarily contiguous to any other block.

In order to interface with C, you can only pass the contents of a single vector. So you'll have to create a single std::vector<int> of size x * y * z. Or you could decide to re-structure your C code to handle a single 1-dimensional stripe of data at a time. Then you could keep the hierarchy, and only pass in the contents of leaf vectors.