Обработка огромных многомерных массивов в C ++ - PullRequest
1 голос
/ 16 декабря 2010

Я разрабатываю игру на C ++, похожую на Minecraft, которая хранит огромное количество данных о местности в памяти. В общем, я хочу сохранить массив в памяти, который является [5] [4] [5] [50] [50] [50]. Это неплохо, поскольку он составляет около 100 МБ виртуальной памяти, так как моя структура будет составлять всего около 8 байт.

Однако у меня проблемы с поиском лучшего способа справиться с этим. Я хочу, чтобы это было в виртуальной памяти, но, очевидно, не в стеке. И я продолжаю ошибаться, каким образом создание этого массива в стеке вызывает переполнение стека. То, что я хотел бы сделать, ниже. Это просто код, который я написал вместе, чтобы дать вам пример того, что я делаю, у меня есть код с правильным синтаксисом на моей машине, я просто не хотел загромождать сообщение.

typedef struct modelBlock
{
    // Information about the blocks
} BLOCK;

typedef struct modelGrid
{
    bool empty;

    BLOCK blocksArray[50][50][50];

} GRID;


class Parent
{
     Child* child;
     Parent(void);
}

Parent::Parent()
{
    Child c;
    child = &c;
}

class Child
{
     GRID grids[5][4][5];
}

Однако каждый раз, когда я делаю это, я вызываю переполнение стека (правильный выбор веб-сайта, верно?). Я играл с использованием массивов, основанных на указателях, но у меня было много проблем с потерями данных за пределами их области видимости.

Если бы кто-нибудь мог дать мне некоторое представление о том, как заставить мои данные храниться в куче, а не в стеке, или если бы я использовал какой-то другой способ создания массива, я был бы очень признателен за помощь. Я хотел бы избежать использования векторов из-за накладных расходов, хотя я не уверен, насколько они существенны.

Ответы [ 8 ]

4 голосов
/ 16 декабря 2010

Использование boost :: multi_array

2 голосов
/ 16 декабря 2010

Если вы хотите выделить что-то в куче, используйте new.

#include <memory>

class Parent
{
    std::auto_ptr<Child> child; // use auto_ptr for dynamically-allocated members
    Parent(const Parent&); // You probably don't want to copy this giant thing
public:
    Parent();
};

Parent::Parent()
  : child(new Child) // initialize members with an initializer list
{
}

Кроме того, избегайте смешивания стилей C и C ++. Там нет причин, чтобы сделать

typedef struct blah{ ... } BLAH;

в C ++. Структура - это просто класс со всеми открытыми по умолчанию членами; так же, как и класс, вы можете ссылаться на имя типа структуры без использования тега struct. Также нет необходимости указывать void для функции, которая не принимает параметров.

boost :: multi_array (ссылка в ответе PigBen) - хороший выбор над необработанными массивами.

1 голос
/ 17 декабря 2010

Меньший пример (с измененными именами для всех структур, чтобы сделать общий принцип более понятным).Структура 'Bloe' - это та, которую вы хотите выделить в куче, и это достигается с помощью 'new'.

   struct Bla {
        int arr[4][4];
   };

   struct Bloe {
        Bla bla[2][2];
   };

   int main()
   {
        Bloe* bloe = new Bloe();
        bloe->bla[1][1].arr[1][1] = 1;
        return 0;
   }
1 голос
/ 16 декабря 2010

Вот кое-что, что работает и может быть построено без зависимости от повышения.Недостатком является то, что он устраняет использование стиля ссылок на элементы [] [] [], но это небольшая стоимость и может быть добавлено.

template<class T>
class Matrix {
    unsigned char* _data;
    const size_t _depth;
    const size_t _cols;
    const size_t _rows;
public:
    Matrix(const size_t& depth, const size_t& rows, const size_t& cols):
        _depth(depth),
        _rows(rows), 
        _cols(cols) {
        _data = new unsigned char [depth * rows * cols * sizeof(T)];
    }
    ~Matrix() {
        delete[] _data;
    }
    T& at(const size_t& depthIndex, const size_t& rowIndex, const size_t& colIndex) const {
        return *reinterpret_cast<T*>(_data + ((((depthIndex * _cols + colIndex) * _rows) + rowIndex) * sizeof(T)));
    }
    const size_t& getDepth() const {
        return _depth;
    }
    const size_t& getRows() const {
        return _rows;
    }
    const size_t& getCols() const {
        return _cols;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    Matrix<int> block(50, 50, 50);
    size_t d, r, c;
    for (d = 0; d < block.getDepth(); d++) {
        for (r = 0; r < block.getRows(); r++) {
            for (c = 0; c < block.getCols(); c++) {
                block.at(d, r, c) = d * 10000000 + r * 10000 + c;
            }
        }
    }
    for (d = 0; d < block.getDepth(); d++) {
        for (r = 0; r < block.getRows(); r++) {
            for (c = 0; c < block.getCols(); c++) {
                assert(block.at(d, r, c) == d * 10000000 + r * 10000 + c);
            }
        }
    }
return 0;
}
1 голос
/ 16 декабря 2010

Чтобы сделать именно то, что вы пытаетесь сделать, вы должны объявить все как указатели (и указатели на указатели на указатели на указатели), а затем распределить каждый из них по отдельности.

Teh sux!

Лучшим вариантом является просто выделить большой блок в одном чанке и использовать несколько переменных вместе с арифметикой указателя для достижения правильного местоположения.

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

1 голос
/ 16 декабря 2010

Если вы хотите, чтобы класс был создан в куче, создайте его с новым:

Child * c = new Child;

и затем, конечно, удалите его, или, что еще лучше, используйте умный указатель.

0 голосов
/ 17 декабря 2010

Ниже я понял, что вы показали, что пытались сделать в своем примере. Я пытался сделать это прямо. Каждый массив из [50], [50], [50] выделяется в одном фрагменте памяти в куче и выделяется только при использовании. Существует также пример кода доступа. Никакого необычного повышения или чего-то особенного, просто базовый C ++.

#include <iostream>

class Block
{
    public:
    // Information about the blocks
    int data;
};


class Grid
{
    public:
    bool empty;
    Block (*blocks)[50][50];

    Grid() : empty(true) {
    }

    void makeRoom(){
        this->blocks = new Block[50][50][50];
        this->empty = false;
    }

    ~Grid(){
        if (!this->empty){
            delete [] this->blocks;
        }
    }
};


class Parent
{
    public:
    Grid (* child)[4][5];

    Parent()
    {
        this->child = new Grid[5][4][5];
    }

    ~Parent()
    {
        delete [] this->child;
    }
};


main(){
    Parent p;
    p.child[0][0][0].makeRoom();
    if (!p.child[0][0][0].empty){
        Block (* grid)[50][50] = p.child[0][0][0].blocks;
        grid[49][49][49].data = 17;
    }

    std::cout << "item = " 
              << p.child[0][0][0].blocks[49][49][49].data 
              << std::endl;
}

Это может быть еще проще и проще и использовать один массив ошибок из блоков [50] [50] [50] [5] [4] [5] в одном куске памяти в куче, но я позволю Вы понимаете, как, если это то, что вы хотите.

Кроме того, динамическое размещение usind в классе Parent имеет единственную цель - использовать кучу вместо стека, но для такого небольшого массива (5 * 4 * 5 указателей) размещение его в стеке не должно быть проблемой, поэтому может быть написано.

class Parent
{
    public:
    Grid child[5][4][5];
};

без каких-либо изменений в способе его использования.

0 голосов
/ 16 декабря 2010

Я сделал это, поместив все данные в двоичный файл. Я рассчитал смещение данных и использовал функции seek () и read () для получения данных, когда это необходимо. Вызов open () очень медленный, поэтому вы должны оставить файл открытым в течение всего срока жизни программы.

...