问题
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];
}
};
回答1:
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
回答2:
In C++ it's not advised to manually allocate memory unless necessary. Let the standard library and templates do the work for you.
They can be very useful and are great to learn if you want to get into C++! You can save a lot of time this way and write some better code.
For example, what is this data type used for? If it fits for your usage, you may instead consider creating a 2D array using std::array
:
std::array<std::array<double, w>, h>
If you need to re-size the arrays regularly, std::vector
could be used instead. It has effectively the same performance as an array, given that is is all it is under the hood. You can reserve()
or resize()
as necessary and push_back
uses a 1.5x increasing scheme and is good at its job.
EDIT: Since size is known, arrays may be better here. Taken suggestion from comments.
回答3:
I recommend you using std::vector. Just wrap it if you want 2D array syntax.
#include <iostream>
#include <vector>
class Array2D {
std::vector<double> _v;
size_t _width;
size_t _height;
public:
Array2D(size_t height, size_t width, double initVal = 0.0)
: _v(width * height, initVal),
_width(width),
_height(height)
{}
double* operator[](size_t y) {
return _v.data() + y * _width;
}
};
int main(int, char**) {
size_t rows = 5;
size_t cols = 3;
Array2D a(rows, cols, 0.2);
for (size_t i = 0; i < cols; ++i)
a[4][i] = -0.1 * i;
std::cout << a[4][2] << std::endl; //-0.2
return 0;
}
回答4:
You can do this, which should work in both C and C++:
double *g2 = (double*) malloc(h * w * sizeof(double));
Though, as others have stated, this is not the way to approach this issue in C++ in general. For instance, you should use a std::vector
instead:
#include <vector>
std::vector<double> g2(h * w);
In both cases, you end up with a dynamically allocated 2D array of double
s in a single contiguous block of memory. And as such, you need to then use g2[(row*w)+col]
syntax to access the individual elements, where 0 <= row < h
and 0 <= col < w
.
来源:https://stackoverflow.com/questions/58122125/porting-c-code-to-c-problem-with-casting-void-from-malloc-to-desired-pointer