Как работать с очень большим 2D-массивом в C++

Мне нужно создать массив 2D int размером 800x800. Но это создает переполнение стека (ха-ха).

Я новичок в C++, так что я должен сделать что-то вроде вектора векторов? И просто инкапсулировать 2d-массив в класс?

в частности, этот массив является моим zbuffer в графической программе. Мне нужно сохранить значение z для каждого пикселя на экране (следовательно, большой размер 800x800).

спасибо!

10 ответов


вам нужно около 2,5 мегабайт, поэтому просто использовать кучу должно быть хорошо. Вам не нужен вектор, если вам не нужно изменить его размер. См.C++ FAQ Lite для примера использования массива кучи" 2D".

int *array = new int[800*800];

(Не забудьте delete[] это, когда вы закончите.)


каждый пост до сих пор оставляет управление памятью для программиста. Этого можно и нужно избегать. ReaperUnreal чертовски близок к тому, что я бы сделал, за исключением того, что я бы использовал вектор, а не массив, а также сделал параметры шаблона измерений и изменил функции доступа-и о просто IMNSHO очистить вещи немного:

template <class T, size_t W, size_t H>
class Array2D
{
public:
    const int width = W;
    const int height = H;
    typedef typename T type;

    Array2D()
        : buffer(width*height)
    {
    }

    inline type& at(unsigned int x, unsigned int y)
    {
        return buffer[y*width + x];
    }

    inline const type& at(unsigned int x, unsigned int y) const
    {
        return buffer[y*width + x];
    }

private:
    std::vector<T> buffer;
};

Теперь вы можете выделить этот 2-D массив в стеке просто отлично:

void foo()
{
    Array2D<int, 800, 800> zbuffer;

    // Do something with zbuffer...
}

надеюсь, это поможет!

EDIT: удаленный массив спецификация от Array2D::buffer. Спасибо Andreas за что!


пример Кевина хорош, однако:

std::vector<T> buffer[width * height];

должно быть

std::vector<T> buffer;

расширяя его немного, вы можете, конечно, добавить оператор-перегрузки вместо at () - функции:

const T &operator()(int x, int y) const
{
  return buffer[y * width + x];
}

и

T &operator()(int x, int y)
{
  return buffer[y * width + x];
}

пример:

int main()
{
  Array2D<int, 800, 800> a;
  a(10, 10) = 50;
  std::cout << "A(10, 10)=" << a(10, 10) << std::endl;
  return 0;
}

вы могли бы сделать вектор векторов, но это будет иметь некоторые накладные расходы. Для Z-буфера более типичным методом было бы создание массива размером 800*800=640000.

const int width = 800;
const int height = 800;
unsigned int* z_buffer = new unsigned int[width*height];

затем получите доступ к пикселям следующим образом:

unsigned int z = z_buffer[y*width+x];

я мог бы создать один массив измерений 800*800. Вероятно, более эффективно использовать одно такое распределение, а не выделять 800 отдельных векторов.

int *ary=new int[800*800];

затем, вероятно, инкапсулируйте это в класс, который действовал как 2D-массив.

class _2DArray
{
  public:
  int *operator[](const size_t &idx)
  {
    return &ary[idx*800];
  }
  const int *operator[](const size_t &idx) const
  {
    return &ary[idx*800];
  }
};

абстракция, показанная здесь, имеет много отверстий, e.g, что произойдет, если вы получите доступ к концу "строки"? Книга " эффективный C++" имеет довольно хорошее обсуждение написания хорошего многомерного массивы в C++.


одна вещь, которую вы можете сделать, это изменить размер стека (если вы действительно хотите, чтобы массив в стеке) с VC флагом для этого [/F] (http://msdn.microsoft.com/en-us/library/tdkhxaks (VS.80).aspx).

но решение, которое вы, вероятно, хотите, это поместить память в кучу, а не в стек, для этого вы должны использовать vector of vectors.

следующая строка объявляет vector из 800 элементов, каждый элемент-это vector из 800 ints и избавляет от ручного управления памятью.

std::vector<std::vector<int> > arr(800, std::vector<int>(800));

обратите внимание на пространство между двумя закрывающими угловыми скобками (> >), который требуется для того, чтобы устранить его из оператора shift right (который больше не будет нужен в C++0x).


или вы можете попробовать что-то вроде:

boost::shared_array<int> zbuffer(new int[width*height]);

вы все равно сможете это сделать:

++zbuffer[0];

больше не беспокойтесь об управлении памятью, нет пользовательских классов, чтобы заботиться, и это легко бросить вокруг.


есть C, как способ сделать:

const int xwidth = 800;
const int ywidth = 800;
int* array = (int*) new int[xwidth * ywidth];
// Check array is not NULL here and handle the allocation error if it is
// Then do stuff with the array, such as zero initialize it
for(int x = 0; x < xwidth; ++x)
{
    for(int y = 0; y < ywidth; ++y)
    {
         array[y * xwidth + x] = 0;
    }
}
// Just use array[y * xwidth + x] when you want to access your class.

// When you're done with it, free the memory you allocated with
delete[] array;

вы можете инкапсулировать y * xwidth + x внутри класса с простым методом get и set (возможно, с перегрузкой [] оператор, если вы хотите начать переход на более продвинутый C++). Я бы рекомендовал добраться до этого медленно, хотя, если вы только начинаете с C++ и не начинаете создавать повторно используемые полностью шаблоны классов для массивов N-измерений, которые просто запутают вас, когда вы начинаете.

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

я обнаружил, что FAQ c++ lite отлично подходит для такой информации. В частности, на ваш вопрос дан ответ by:

http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.16


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

int array[800][800];

void fn()
{
    static int array[800][800];
}

таким образом, он не пойдет в стек, и вам не придется иметь дело с динамической памятью.


ну, основываясь на том, что начал Найл Райан, если производительность является проблемой, вы можете сделать этот шаг дальше, оптимизировав математику и инкапсулировав это в класс.

Итак, начнем с немного математики. Напомним, что 800 можно записать в степени 2 как:

800 = 512 + 256 + 32 = 2^5 + 2^8 + 2^9

таким образом, мы можем написать нашу функцию адресации как:

int index = y << 9 + y << 8 + y << 5 + x;

поэтому, если мы инкапсулируем все в хороший класс, мы получаем:

class ZBuffer
{
public:
    const int width = 800;
    const int height = 800;

    ZBuffer()
    {
        for(unsigned int i = 0, *pBuff = zbuff; i < width * height; i++, pBuff++)
            *pBuff = 0;
    }

    inline unsigned int getZAt(unsigned int x, unsigned int y)
    {
        return *(zbuff + y << 9 + y << 8 + y << 5 + x);
    }

    inline unsigned int setZAt(unsigned int x, unsigned int y, unsigned int z)
    {
        *(zbuff + y << 9 + y << 8 + y << 5 + x) = z;
    }
private:
    unsigned int zbuff[width * height];
};