Caos21 Caos21 - 1 month ago 11
C++ Question

Boost.MultiArray as a member of a class: resize and copy assertion crash

To show the problem, let suppose you have a class called basic_ma and has a member which is a MultiArray (MA). Also suppose that in the constructor (or in a method) you have some computations in a temporary MA (called

tmp
here) and you want to copy the contents of
tmp
to
ma
(this behavior is desired, i would like to have at the same time
tmp
and
ma
).

// A class with a MultiArray member
class basic_ma {
public:
basic_ma() { }

// initialize ma
basic_ma(size_t x, size_t y) {
// I would like to compute in tmp and then copy to member ma
array2d tmp(boost::extents[x][y]);
// dummy stuff, some serious calculation in real life
for(size_t i=0; i<tmp.shape()[0]; ++i) {
for(size_t j=0; j<tmp.shape()[1]; ++j) {
tmp[i][j] = (i+1.0)*(j+1.0);
}
}
ma.resize(boost::extents[tmp.shape()[0]][tmp.shape()[1]]);
ma = tmp;
// Alternative function to copy
//copy_ma(tmp, ma);
}

void show() {
cout << endl << "> Show ma" << endl;
for(size_t i = 0; i < ma.shape()[0]; ++i) {
cout << "> ";
for(size_t j = 0; j < ma.shape()[1]; ++j) {
cout << ma[i][j] << '\t';
}
cout << endl;
}
cout << endl;
}
private:
array2d ma;// MultiArray member
};


Now consider another class which member is the previous class.

// Another class has several (possibly a vector of) basic_ma's
class group_ma {
public:
group_ma() { }
// constructs basic_ma's
group_ma(size_t x, size_t y) {
bma1 = basic_ma(x, y);
bma1.show();
bma2 = basic_ma(y, x);
bma2.show();
}
private:
basic_ma bma1;
basic_ma bma2;
};


Then an assertion problem occurs when I instantiate the
group_ma
using the constructor with two parameters:

a.out: /usr/include/boost/multi_array/multi_array_ref.hpp:484: boost::multi_array_ref<T, NumDims>& boost::multi_array_ref<T, NumDims>::operator=(const ConstMultiArray&) [with ConstMultiArray = boost::multi_array<double, 2ul>; T = double; long unsigned int NumDims = 2ul]: Assertion `std::equal(other.shape(),other.shape()+this->num_dimensions(), this->shape())' failed.
Aborted (core dumped)


Here is the
main
:

int main(int argc, char **argv) {

basic_ma bma(3,5);// OK compile and run
bma.show();// OK compile and run

group_ma gma1;// OK compile and run

gma1 = group_ma(3, 3);// Compile but assertion error when running

group_ma gma2(4, 2);// Compile but assertion error when running

unique_ptr<group_ma> gma_ptr = unique_ptr<group_ma>(new group_ma(5, 6));
// Compile but assertion error when running

return 0;
}


Compiled with
gcc version 6.2.1
:

g++ main.cpp -std=gnu++11 -pedantic -Wall


The
basic_ma
works fine, the problem occurs when using
group_ma
.
I also tried another function to copy the MA's with the same result:

// copy Boost.MultiArray source to dest
void copy_ma(const array2d source, array2d& dest) {
vector<size_t> grid;
const size_t* shape = source.shape();
grid.assign(shape, shape+source.num_dimensions());
dest.resize(grid);
for(size_t i=0; i<source.shape()[0]; ++i) {
for(size_t j=0; j<source.shape()[1]; ++j) {
dest[i][j] = source[i][j];
}
}
}


I do not see what i'm doing wrong.
Using the
-DNDEBUG
flag raises a memory error:
malloc(): memory corruption (fast)


The whole code is here:

#include <iostream>
#include <boost/multi_array.hpp>
#include <memory>

using namespace std;

typedef boost::multi_array<double, 2> array2d;

// A class with a MultiArray member
class basic_ma {
public:
basic_ma() { }
// initialize ma
basic_ma(size_t x, size_t y) {
// I would like to compute in tmp and the copy to member ma
array2d tmp(boost::extents[x][y]);
// dummy stuff
for(size_t i=0; i<tmp.shape()[0]; ++i) {
for(size_t j=0; j<tmp.shape()[1]; ++j) {
tmp[i][j] = (i+1.0)*(j+1.0);
}
}
ma.resize(boost::extents[tmp.shape()[0]][tmp.shape()[1]]);
ma = tmp;
// Alternative function to copy
// copy_ma(tmp, ma);
}
void show() {
cout << endl << "> Show ma" << endl;
for(size_t i = 0; i < ma.shape()[0]; ++i) {
cout << "> ";
for(size_t j = 0; j < ma.shape()[1]; ++j) {
cout << ma[i][j] << '\t';
}
cout << endl;
}
cout << endl;
}
private:
array2d ma;// MultiArray member
};


// copy Boost.MultiArray source to dest
void copy_ma(const array2d source, array2d& dest) {
vector<size_t> grid;
const size_t* shape = source.shape();
grid.assign(shape, shape+source.num_dimensions());
dest.resize(grid);
for(size_t i=0; i<source.shape()[0]; ++i) {
for(size_t j=0; j<source.shape()[1]; ++j) {
dest[i][j] = source[i][j];
}
}
}

// Another class will have several (possibly a vector of) basic_ma's
class group_ma {
public:
group_ma() { }
// constructs basic_ma's
group_ma(size_t x, size_t y) {
bma1 = basic_ma(x, y);
bma1.show();
bma2 = basic_ma(y, x);
bma2.show();
}
private:
basic_ma bma1;
basic_ma bma2;
};

int main(int argc, char **argv) {

basic_ma bma(3,5);// OK compile and run
bma.show();// OK compile and run

group_ma gma1;// OK compile and run

gma1 = group_ma(3, 3);// Compile but assertion error when running

group_ma gma2(4, 2);// Compile but assertion error when running

unique_ptr<group_ma> gma_ptr = unique_ptr<group_ma>(new group_ma(5, 6));
// Compile but assertion error when running

return 0;
}

Answer

The error message says that the arrays are not the same size during assignment. There are several ways to fix it. In the group_ma use the constructor:

group_ma(size_t x, size_t y)
    :bma1( x, y )
    ,bma2( y, x )
{
    //bma1 = basic_ma(x, y);
    bma1.show();
    //bma2 = basic_ma(y, x);
    bma2.show();
}

the default constructor did not give you a 3x3. Boost docs talk about resizing a multi_array and you could do that if you must assign. But with the caveat that the dimentions remain the same.

Adding the member to basic_ma

void resize(size_t x, size_t y )
{
    ma.resize( boost::extents[x][y] );
}

then:

basic_ma not_initialized;
not_initialized.resize( 3, 3 );
basic_ma bma(3,3);
bma.show();
not_initialized= bma; // now this works
not_initialized.show( );

Another thought, you could do the resize in your own assignment operator...

So if you want the basic_ma to remain private. make group_ma a friend of basic_ma. Then you can add this to group_ma

void operator = ( group_ma& source )
{
    bma1.resize( source.bma1.ma.shape( )[ 0 ], source.bma1.ma.shape( )[ 1 ] );
    bma2.resize( source.bma2.ma.shape( )[ 0 ], source.bma2.ma.shape( )[ 1 ] );
    bma1= source.bma1;
    bma2= source.bma2;
}

Then this works:

group_ma gma1;
gma1 = group_ma(3, 3);
group_ma gma2(4, 2);
gma1= gma2;

There may be better ways, I don't know as I haven't looked. But I think this may suit you.

And yet another thought and better if you have different kinds of groups. Do the resize in an assignment operator of basic_ma. Then in the group assignment you only need:

void operator = ( group_ma& source )
{
    bma1= source.bma1;
    bma2= source.bma2;
}