I'm trying to construct a class derived from std::valarray<T>
to use my own methods on it. I have encountered a problem about assigning values using operator[]. After a lot of effort, I think I finally detected the problem. While assigning to std::slice_array<T>
does not constitude a problem, assigning to std::gslice_array<T>
does.
Here is the code to reproduce the problem:
#include <valarray>
#include <cassert>
#include <iostream>
int main() {
const size_t rows = 16;
const size_t cols = 24;
assert(rows%8 == 0);
assert(cols%8 == 0);
// return b_th 8x8 block
auto get_block = [&rows, &cols](size_t b) -> std::gslice {
return std::gslice((b / (cols/8))*8*cols + (b % (cols/8))*8, {8, 8}, {cols, 1});
};
// zeros(rows, cols) but 1D
std::valarray<int> v(0, rows*cols);
auto print = [&rows, &cols, &v]() -> void {
for (size_t i=0; i<rows; i++) {
for (size_t j=0; j<cols; j++)
std::cout << " " << v[i*cols + j];
std::cout << "\n";
}
std::cout << std::endl;
};
print();
// this is OK
v[get_block(1)] = 1;
print();
// this is also OK
std::slice_array<int> s = v[std::slice(2*cols, cols, 1)];
s = 2;
print();
// ???
std::gslice_array<int> g = v[get_block(3)];
// g = 3; // this line causes runtime error
print();
return 0;
}
Any idea how to solve this issue?
If you use g++, you have dangling reference, which leads to undefined behaviour and crash.
It is a bug in g++.
In this line:
std::gslice_array<int> g = v[get_block(3)];
by calling get_block(3)
temporary instance of gslice
is created.
Method valarray::operator[](gslice)
is called taking this temporary gslice
.
Let's look at its implementation:
template<typename _Tp>
inline gslice_array<_Tp>
valarray<_Tp>::operator[](const gslice& __gs)
{
return gslice_array<_Tp> (_Array<_Tp>(_M_data), __gs._M_index->_M_index);
}
it takes reference to const gslice
so temporary instance of gslice
can be bound to __gs
,
when returning from this method, object of gslice_array
is created by:
template<typename _Tp>
inline
gslice_array<_Tp>::gslice_array(_Array<_Tp> __a,
const valarray<size_t>& __i)
: _M_array(__a), _M_index(__i) {}
where _M_array
and _M_index
of gslice_array
are defined as:
_Array<_Tp> _M_array;
const valarray<size_t>& _M_index; // <------------- here is reference !
To _M_index
of gslice_array
is assigned __gs._M_index->_M_index
. What is __gs._M_index
?
It is a inner struct of gslice
named as _Indexer
. It uses a reference counter mechanism
to prolong lifetime itself. Reference counter of _Indexer
can be increased only if
gslice
is copied (by copy constructor or copy assignment operator), but none of these
operation are performed in this code. Hence, when gslice
as temporary object is deleted, _M_index
of __gs
is deleted too, and finally dangling reference occurs.
Where is bug? Keeping reference to member of indexer, which is deleted when gslice
is deleted.
It would not happen, if indexer held pointer to valarray instead of reference.
Storing of references is not transitional.
As workaround, create L-value instance of gslice
as follows:
auto b = get_block(3);
std::gslice_array<int> g = v[b];
g = 3;
print();
then, everything works fine.