Как обойти очень большой 2d массив в C ++ - PullRequest
8 голосов
/ 15 сентября 2008

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

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

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

Спасибо!

Ответы [ 10 ]

11 голосов
/ 15 сентября 2008

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

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

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

10 голосов
/ 15 сентября 2008

Каждый пост до сих пор оставляет управление памятью для программиста. Этого можно и нужно избегать. 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;
};

Теперь вы можете разместить этот двумерный массив в стеке просто:

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

    // Do something with zbuffer...
}

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

EDIT: удалена спецификация массива из Array2D::buffer. Спасибо Андреасу за то, что он это понял!

4 голосов
/ 15 сентября 2008

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

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;
}
2 голосов
/ 15 сентября 2008

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

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

Тогда, вероятно, инкапсулируйте это в классе, который действовал как двумерный массив.

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

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

2 голосов
/ 15 сентября 2008

Вы могли бы сделать вектор векторов, но это потребовало бы некоторых накладных расходов. Для 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];
1 голос
/ 15 сентября 2008

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

int array[800][800];

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

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

1 голос
/ 15 сентября 2008

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

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

Вы все еще должны быть в состоянии сделать это тоже:

++zbuffer[0];

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

1 голос
/ 15 сентября 2008

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

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

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

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

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

1 голос
/ 15 сентября 2008

Есть способ, подобный 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-измерения, которые просто запутают вас, когда вы начинаете.

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

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

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

0 голосов
/ 15 сентября 2008

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

Итак, начнем с математики. Напомним, что 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];
};
...