I am currently porting some C code I wrote to C++ for fun. I am struggling with a malloc()
call I make in C, with h
and w
being constants for simplicity reasons, but later exchanged with runtime constants:
double (*g2)[h][w] = malloc(h * w * sizeof(double));
In C, this is an implicit conversion of a void*
, and this of course doesn't fly with C++.
I already tried casting this with reinterpret_cast<double[h][w]>
, but this is still an invalid cast.
I was wondering, how can I make this work in C++ since this would save me a lot of work?
As an alternative I'll probably use a matrix class with indirection:
struct Matrix : std::vector<double> {
unsigned matSize;
std::vector<double*> indirection;
Matrix() : matSize(0) {}
Matrix(unsigned n) : matSize(n) {
resize(n*n);
indirection.resize(n);
for(unsigned i = 0; i < n; ++i) {
indirection[i] = &(*this)[i*n];
}
}
double& operator()(unsigned i, unsigned j) {
return indirection[i][j];
}
const double& operator()(unsigned i, unsigned j) const {
return indirection[i][j];
}
};
Porting involves more than just making it work, line by line, so:
C:
double (*g2)[h][w] = malloc(h * w * sizeof(double));
...
g2[y][x] = ...;
C++:
std::vector<double> g2(h*w);
...
g2[y+x*h] = ...; // or
g2[y*w+x] = ...;
Using that syntax is inconvenient for accessing elements so you might want to wrap it inside a simple class. Example:
#include <iostream>
#include <iterator>
#include <vector>
class arr2d {
public:
arr2d(size_t h, size_t w) : data_(h * w), w_(w) {}
inline double& operator()(size_t y, size_t x) {
return data_[y * w_ + x];
}
inline double operator()(size_t y, size_t x) const {
return data_[y * w_ + x];
}
// getting pointer to a row
inline double* operator[](size_t y) {
return &data_[y * w_];
}
inline double const* operator[](size_t y) const {
return &data_[y * w_];
}
inline size_t width() const { return w_; }
private:
std::vector<double> data_;
size_t w_;
};
int main() {
arr2d g2(3, 4);
g2(2, 3) = 3.14159;
// alternative access:
g2[1][2] = 1.23456;
std::cout << g2[2][3] << "\n";
double* row = g2[2];
std::copy(row, row + g2.width(), std::ostream_iterator<double>(std::cout, ", "));
std::cout << "\n";
}
Output:
3.14159
0, 0, 0, 3.14159,
A non-initializing version could look like:
class arr2d {
public:
arr2d(size_t h, size_t w) : data_(new double[w * h]), w_(w) {}
inline double& operator()(size_t y, size_t x) { return data_[y * w_ + x]; }
inline double operator()(size_t y, size_t x) const { return data_[y * w_ + x]; }
inline double* operator[](size_t y) { return &data_[y * w_]; }
inline double const* operator[](size_t y) const { return &data_[y * w_]; }
inline size_t width() const { return w_; }
private:
std::unique_ptr<double[]> data_;
size_t w_;
};
But note that thestd::copy(row, row + g2.width(), std::ostream_iterator<double>(std::cout, ", "));
from the first example would lead to undefined behaviour.
Also note that this version will delete the copy constructor and copy assignment operator. You'll have to implement them yourself if you need them.
The creation time for the non-initializing version is of course hard to beat with any initializing version, but for access times, one might think that a lookup table, or indirection as you call it, for the rows would speed up things compared to doing the multiplication and addition in one go.
My results: 8x8
http://quick-bench.com/f8zcnU9P8oKwMUwLRXYKZnLtcLM1024x1024
http://quick-bench.com/0B2rQeUkl-WoqGeG-iS1hdP4ah84096x4096
http://quick-bench.com/c_pGFmB2C9_B3r3aRl7cDK6BlxU
It seems to vary. The lookup version is faster for the 4096x4096 matrix but the naive version is faster for the two smaller ones. You need to compare using sizes close to what you'll be using and also check with different compilers. I sometimes get completely opposite "winners" when changing compiler.
Since you don't mind inheriting from std::vector
or keeping extra data for a lookup-table, this could be an option. It seems to outperform the other versions slightly.
class arr2d : protected std::vector<double*> {
public:
using std::vector<double*>::operator[]; // "row" accessor from base class
arr2d(size_t h, size_t w) :
std::vector<double*>(h),
data_(new double[w * h]),
w_(w),
h_(h)
{
for(size_t y = 0; y < h; ++y)
(*this)[y] = &data_[y * w];
}
inline size_t width() const { return w_; }
inline size_t height() const { return h_; }
private:
std::unique_ptr<double[]> data_;
size_t w_, h_;
};
Here are Philipp-P's (OP:s) own measurements for the different 2D-array implementations:
8x8
http://quick-bench.com/vMS6a9F_KrUf97acWltjV5CFhLY1024x1024
http://quick-bench.com/A8a2UKyHaiGMCrf3uranwOCwmkA4096x4096
http://quick-bench.com/XmYQc0kAUWU23V3Go0Lucioi_Rg
Results for 5-point stencil code for the same versions: 8x8
http://quick-bench.com/in_ZQTbbhur0I4mu-NIquT4c0ew1024x1024
http://quick-bench.com/tULLumHZeCmC0HUSfED2K4nEGG84096x4096
http://quick-bench.com/_MRNRZ03Favx91-5IXnxGNpRNwQ
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With