Porting C code to C++, problem with casting void* from malloc to desired pointer

元气小坏坏 提交于 2020-06-25 21:12:55

问题


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 the
std::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/f8zcnU9P8oKwMUwLRXYKZnLtcLM
1024x1024 http://quick-bench.com/0B2rQeUkl-WoqGeG-iS1hdP4ah8
4096x4096 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_KrUf97acWltjV5CFhLY
1024x1024 http://quick-bench.com/A8a2UKyHaiGMCrf3uranwOCwmkA
4096x4096 http://quick-bench.com/XmYQc0kAUWU23V3Go0Lucioi_Rg

Results for 5-point stencil code for the same versions:
8x8 http://quick-bench.com/in_ZQTbbhur0I4mu-NIquT4c0ew
1024x1024 http://quick-bench.com/tULLumHZeCmC0HUSfED2K4nEGG8
4096x4096 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 doubles 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!