将 C 代码移植到 C++,将 void* 从 malloc 转换为所需指针的问题

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

为了好玩,我目前正在将我编写的一些 C 代码移植到 C++。我正在努力处理我在 C 中进行的 malloc() 调用,出于简单原因,hw 是常量,但后来与运行时常量交换:

double (*g2)[h][w] = malloc(h * w * sizeof(double));

在 C 中,这是一个 void* 的隐式转换,这当然不适用于 C++。

我已经尝试用 reinterpret_cast<double[h][w]> 进行转换,但这仍然是一个无效的转换。

我想知道,我怎样才能在 C++ 中完成这项工作,因为这会节省我很多工作?

作为替代方案,我可能会使用间接矩阵 class:

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];
    }
};

移植不仅仅是让它一行一行地工作,所以:

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] = ...;

使用该语法访问元素不方便,因此您可能希望将其包装在一个简单的 class 中。示例:

#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";
}

输出:

3.14159
0, 0, 0, 3.14159,

非初始化版本可能如下所示:

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_;
};

但请注意
std::copy(row, row + g2.width(), std::ostream_iterator<double>(std::cout, ", "));
第一个例子会导致未定义的行为。

另请注意,此版本将删除复制构造函数和复制赋值运算符。如果需要,您必须自己实现它们。

非初始化版本的创建时间当然很难与任何初始化版本相提并论,但对于访问时间,人们可能会认为查找 table,或您所说的间接寻址,对于与一次进行乘法和加法相比,这些行会加快速度。

我的结果:
8x8 http://quick-bench.com/f8zcnU9P8oKwMUwLRXYKZnLtcLM
1024x1024http://quick-bench.com/0B2rQeUkl-WoqGeG-iS1hdP4ah8
4096x4096http://quick-bench.com/c_pGFmB2C9_B3r3aRl7cDK6BlxU

似乎有所不同。对于 4096x4096 矩阵,查找版本更快,但对于两个较小的矩阵,朴素版本更快。您需要比较使用接近您将要使用的大小,并检查不同的编译器。在更改编译器时,我有时会得到完全相反的“赢家”。

由于您不介意从 std::vector 继承或保留额外数据以供查找 -table,这可能是一个选项。它似乎略胜于其他版本。

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_;
};

以下是 Philipp-P (OP:s) 自己对不同二维阵列实现的测量:

8x8 http://quick-bench.com/vMS6a9F_KrUf97acWltjV5CFhLY
1024x1024http://quick-bench.com/A8a2UKyHaiGMCrf3uranwOCwmkA
4096x4096http://quick-bench.com/XmYQc0kAUWU23V3Go0Lucioi_Rg

相同版本的 5 点模板代码结果:
8x8 http://quick-bench.com/in_ZQTbbhur0I4mu-NIquT4c0ew
1024x1024http://quick-bench.com/tULLumHZeCmC0HUSfED2K4nEGG8
4096x4096http://quick-bench.com/_MRNRZ03Favx91-5IXnxGNpRNwQ

在 C++ 中,除非必要,否则不建议手动分配内存。让标准库和模板为您完成工作。

如果您想学习 C++,它们可能非常有用并且非常适合学习!您可以通过这种方式节省大量时间并编写更好的代码。

例如,这个数据类型是做什么用的?如果适合您的使用,您可以考虑使用 std::array:

创建一个二维数组

std::array<std::array<double, w>, h>

如果您需要定期调整数组大小,可以改用 std::vector。它实际上具有与数组相同的性能,因为这就是它的全部内容。您可以根据需要 reserve()resize() 并且 push_back 使用 1.5 倍增加的方案并且擅长它的工作。

编辑:由于大小已知,数组在这里可能更好。从评论中采纳建议。

你可以这样做,它应该在 C 和 C++ 中都有效:

double *g2 = (double*) malloc(h * w * sizeof(double));

不过,正如其他人所说,这不是一般在 C++ 中解决此问题的方法。例如,您应该使用 std::vector 代替:

#include <vector>
std::vector<double> g2(h * w);

在这两种情况下,您最终都会在一个连续的内存块中得到动态分配的 double 二维数组。因此,您需要使用 g2[(row*w)+col] 语法来访问各个元素,其中 0 <= row < h0 <= col < w.

我建议您使用 std::vector。如果你想要二维数组语法,就把它包装起来。

#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;
}