Портирование кода C на C ++, проблема с приведением void * из malloc к нужному указателю - PullRequest
4 голосов
/ 26 сентября 2019

В настоящее время я портирую некоторый код на C, написанный для C ++.Я борюсь с вызовом malloc(), который я делаю в C, с h и w, являющимися константами по соображениям простоты, но позже их заменяют на константы времени выполнения:

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

В C этонеявное преобразование void*, и это, конечно, не работает с C ++.

Я уже пытался привести его к reinterpret_cast<double[h][w]>, но это все еще недопустимое приведение.

Iбыло интересно, как я могу сделать эту работу в C ++, так как это сэкономило бы мне много работы?

В качестве альтернативы я, вероятно, буду использовать матричный класс с косвенным указанием:

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

Ответы [ 4 ]

7 голосов
/ 26 сентября 2019

Портирование включает в себя больше, чем просто работу, построчно, поэтому:

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

Использование этого синтаксисанеудобен для доступа к элементам, поэтому вы можете захотеть обернуть его внутри простого класса.Пример:

#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, ", "));
из первого примера приведет к неопределенному поведению.

Также обратите внимание, что в этой версии будут удалены конструктор копирования и оператор назначения копирования.Вы должны будете реализовать их самостоятельно, если они вам нужны.

Время создания для неинициализируемой версии, конечно, трудно превзойти с любой инициализирующей версией, но для времени доступа можно подумать, что поисктаблица, или косвенное указание, как вы ее называете, поскольку строки ускорят процесс по сравнению с умножением и сложением за один раз.

Мои результаты:
8x8 http://quick -benchCOM

Кажется, меняется.Версия для поиска быстрее для матрицы 4096x4096, но наивная версия быстрее для двух меньших.Вы должны сравнить, используя размеры, близкие к тому, что вы будете использовать, а также проверить с различными компиляторами.Иногда я получаю совершенно противоположные «победители» при смене компилятора.

Поскольку вы не возражаете против наследования от std::vector или сохранения дополнительных данных для таблицы соответствия, это может быть вариантом.Кажется, он немного превосходит другие версии.

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) для различных реализаций 2D-массива:

8x8 http://quick -bench.com / vMS6a9F_KrUf97acWltjV5CFhLY
1024x1024 http://quick -bench.com / A8a2UKyHaiGMCrf3uranwOCwmkA
4096x4096 * 1063ch1064646464XmYQc0kAUWU23V3Go0Lucioi_Rg

Результаты для 5-точечного кода трафарета для тех же версий:
8x8 http://quick -bench.com / in_ZQTbbhur0I4mu-NIquT4c0 * 1072 *1072* 1072 *1072* http://quick -bench.com / tULLumHZeCmC0HUSfED2K4nEGG8
4096x4096 http://quick -bench.com / _MRNRZ03Favx91-5IXnxGNpRNwQ

3 голосов
/ 26 сентября 2019

Я рекомендую использовать std :: vector.Просто оберните его, если вы хотите синтаксис 2D-массива.

#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;
}
3 голосов
/ 26 сентября 2019

Вы можете сделать это, что должно работать как на 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 < h и 0 <= col < w.

3 голосов
/ 26 сентября 2019

В C ++ не рекомендуется выделять память вручную без необходимости.Пусть стандартная библиотека и шаблоны сделают всю работу за вас.

Они могут быть очень полезны, и их очень полезно узнать, если вы хотите попасть в C ++!Таким образом вы можете сэкономить много времени и написать более качественный код.

Например, для чего используется этот тип данных?Если он подходит для вашего использования, вы можете вместо этого рассмотреть возможность создания 2D-массива, используя std::array:

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

Если вам необходимо регулярно изменять размеры массивов, std::vector можетиспользовать вместоОн имеет практически ту же производительность, что и массив, учитывая, что это все, что у него под рукой.При необходимости вы можете reserve() или resize(), а push_back использует схему увеличения в 1,5 раза и хорошо справляется со своей задачей.

РЕДАКТИРОВАТЬ: поскольку размер известен, массивы могут быть лучше.Взял предложение из комментариев.

...