0

I'm trying to copy a 3D array into a 3D vector that has the same dimensioning as the array. There's no problem if I use a nested loop to make the copy, and the program compiles OK if I try to use std::copy, but the program throws an exception when it runs (access violation in memcpy.asm at CopyUpDword Loop). Obviously I'm going out of bounds somewhere, but why?

Using a nested loop to copy the array into the vector and examining the vector before and after the copy shows that the vector is sized properly.

#include <string>
#include <vector>
#include <algorithm>
using namespace std;

typedef vector<string> vsCols;
typedef vector<vsCols> vsRows;
typedef vector<vsRows> vsPage;

string Array[2][3][2];

int main()
{

// dimension the vector same as the array
vsPage pages(2);

for (size_t page = 0; page < pages.size(); page++) {
    pages[page].resize(3);
    for (size_t row = 0; row < pages[page].size(); row++) {
        pages[page][row].resize(2);
    }
}

// fill Array
string s;
    for (int page = 0; page < 2; page++) {
        for (int row = 0; row < 3; row++) {
            for (int col = 0; col < 2; col++) {
        s = "Item" + to_string(((page + 1) + (row + 2)) * (col + 1));
        Array[page][row][col] = s;
            }
        }
    }

// throws the exception described above
copy(&Array[0][0][0], &Array[0][0][0] + 2 * 3 * 2, &pages[0][0][0]);

The syntax is similar to an example I saw here that used std::copy to copy one 3D array to another 3D array, so I expected it to also work with a vector as the copy destination.

  • Unrelated: A faster way to build `pages`: `vsPage pages(2, vsRows(3, vsCols(2)));` – user4581301 Oct 12 '19 at 03:17
  • 1
    A `vector` of `vector`s is not contiguous. `&pages[0][0][0]` gives the address of the first column of the first row of the first page. Crom only knows where in memory the second column of the first row of the first page is, but the odds of it being immediately after the first one are astonishingly small. That's almost certainly the access violation. – user4581301 Oct 12 '19 at 03:21

1 Answers1

1

A vector of vectors is not contiguous. Each vector, and you have 12 of them not including the vectors stitching the 12 together, holds it's own block of dynamically allocated memory. &pages[0][0][0] gives the address of the first column of the first row of the first page. Crom only knows where in memory the second column of the first row of the first page is, but the odds of it being immediately after the first one are astonishingly small. That's almost certainly the access violation.

With the vector of vectors approach, the best you can hope for is this something like:

vsPage pages;
for (const auto & row : Array)
{
    vsRows r;
    for (const auto & col : row)
    {
        r.emplace_back(std::begin(col), std::end(col));
    }
    pages.emplace_back(r);

}

You can really only copy one column at a time.

So it's probably time to ditch vector of vectors and make a vector LOOK like a multi-dimensional vector. This is a good 2-D design that you should be able to beat into 3 dimensions. Once the data is all contiguous, you can cheat like hell and std::copy all of the data in the way the asker attempted. I'd try to hide this out of site inside the 3-D class so that you can change it up later without disturbing everyone else.

Example:

// Basic framework gleefully looted from jamesdlin
class ThreeDee
{
public:
    // zero initialized "empty" 3D matrix
    ThreeDee(size_t pages, size_t rows, size_t cols) :
            mPages(pages), mRows(rows), mCols(cols), mData(pages * rows * cols)
    {
    }


    // array initialized "empty" 3D matrix
    // the template params are deduced from the parameters of the array
    // but to get an array rather than a decayed pointer, we need to pass the array 
    // by reference
    template<size_t PAGES, size_t ROWS, size_t COLS>
    ThreeDee(string (&arr)[PAGES][ROWS][COLS]) :
            mPages(PAGES), mRows(ROWS), mCols(COLS), mData(PAGES * ROWS * COLS)
    {
        // I hate this, but I don't have anything better yet and I have to 
        // surrender the computer to a student with homework
        std::copy(&arr[0][0][0], &arr[0][0][0]+mData.size(), mData.data());
    }

    string& operator()(size_t page, size_t row, size_t col)
    {
        return mData[(page * mRows + row) * mCols + col]; // 3D to 1D mapping 
    }

    string operator()(size_t page, size_t row, size_t col) const
    {
        return mData[(page * mRows + row) * mCols + col];
    }

    size_t pages()const
    {
        return mPages;
    }
    size_t rows()const
    {
        return mRows;
    }
    size_t cols()const
    {
        return mCols;
    }

private:
    size_t mPages;
    size_t mRows;
    size_t mCols;
    std::vector<string> mData;
};

This makes the usage simple:

string Array[2][3][2];
ThreeDee pages(Array);
user4581301
  • 33,082
  • 7
  • 33
  • 54