std :: vector и непрерывная память многомерных массивов

Я знаю, что стандарт не заставляет std::vector выделять непрерывные блоки памяти, но, тем не менее, все реализации подчиняются этому.

Предположим, я хочу создать вектор многомерного статического массива. Рассмотрим 2 измерения для простоты и вектор длины N. То есть я хочу создать вектор из N элементов, скажем, int[5].

Могу ли я быть уверен, что все N * 5 целых чисел теперь непрерывны в памяти? Чтобы я в принципе мог получить доступ ко всем целым числам, просто зная адрес первого элемента? Зависит ли эта реализация?

Для справки, способ, которым я в настоящее время создаю 2D-массив в непрерывном блоке памяти, заключается в том, чтобы сначала создать (динамический) массив с плавающей запятой * длины N, выделить все N * 5 с плавающей запятой в один массив, а затем скопировать адрес каждого 5-го элемента в первый массив float*.


person user787267    schedule 24.06.2011    source источник
comment
Я знаю, что стандарт не заставляет std::vector выделять непрерывные блоки памяти - Да, начиная с C ++ 03.   -  person kennytm    schedule 24.06.2011
comment
@KennyTM: Не знал, что этого не было в C ++ 98. Спасибо. Я предполагаю, что это все еще было бы практическим требованием для удовлетворения заявленного мандата сложности операции для доступа к элементам, верно? Скорее похоже на то, как std::string на практике всегда имел непрерывное хранилище элементов, несмотря на то, что это не было явно предписано до C ++ 0x.   -  person Lightness Races in Orbit    schedule 24.06.2011


Ответы (6)


Для справки, способ, которым я в настоящее время создаю 2D-массив в непрерывном блоке памяти, заключается в том, чтобы сначала создать (динамический) массив с плавающей запятой * длины N, выделить все N * 5 с плавающей запятой в один массив, а затем скопировать адрес каждого 5-го элемента в первый массив float *.

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

float (*p)[5] = new float[N][5];

p [0] [0] = 42;   // access first element
p[N-1][4] = 42;   // access last element

delete[] p;

Обратите внимание, что есть только одно распределение. Могу я предложить прочитать больше об использовании массивов в C ++?

person fredoverflow    schedule 24.06.2011
comment
@Tom: Хм, выглядит восхитительно! - person fredoverflow; 24.06.2011

Стандартный действительно требует, чтобы память std::vector была непрерывной. С другой стороны, если вы напишете что-то вроде:

std::vector<std::vector<double> > v;

глобальная память (вся v[i][j]) не будет непрерывной. Обычный способ создания 2D-массивов - использовать один

std::vector<double> v;

и рассчитайте индексы точно так же, как вы предлагаете сделать с float. (Вы также можете создать второй std::vector<float*> с адресами, если хотите. Однако я всегда просто пересчитывал индексы.)

person James Kanze    schedule 24.06.2011
comment
+1, для первоначальной оценки вы можете рассмотреть этот пример в C ++ FAQ lite. - person David Rodríguez - dribeas; 24.06.2011
comment
Дэвид: хорошая ссылка. Каждого, кто любит 2D-массивы, следует заставить прочитать эту и следующие записи, пока они не смогут повторить их во сне ;-) - person FrankH.; 24.06.2011

Согласно стандарту C ++ элементы вектора гарантированно являются смежными.
Цитаты из стандарта следующие:

Из n2798 (черновик C ++ 0x):

23.2.6 Вектор шаблона класса [вектор]

1 Вектор - это контейнер последовательности, который поддерживает итераторы произвольного доступа. Кроме того, он поддерживает (амортизированные) операции вставки и стирания с постоянным временем в конце; вставка и стирание в середине занимают линейное время. Управление хранилищем осуществляется автоматически, хотя могут быть даны подсказки для повышения эффективности. Элементы вектора хранятся непрерывно, что означает, что если v - вектор, где T - какой-то тип, отличный от bool, то он подчиняется тождеству & v [n] == & v [0] + n для всех 0 ‹= n‹ v .size ().

Стандарт C ++ 03 (23.2.4.1):

Элементы вектора хранятся непрерывно, что означает, что если v - вектор, где T - некоторый тип, отличный от bool, то он подчиняется тождеству & v [n] == & v [0] + n для всех 0 ‹= n ‹v.size ().

Также см. здесь каковы взгляды Херба Саттера на то же самое.

person Alok Save    schedule 24.06.2011
comment
Да здравствует std :: vector ‹bool›: D: D: D - person Armen Tsirunyan; 24.06.2011
comment
Но C ++ 0X еще не является официальным стандартом. - person user787267; 24.06.2011

Как уже отмечал @Als, да, std::vector (сейчас) гарантирует непрерывное распределение. Однако я бы не моделировал 2D-матрицу с помощью массива указателей. Вместо этого я бы рекомендовал один из двух подходов. Проще (намного) - просто использовать operator() для индексации и выполнить умножение, чтобы преобразовать 2D-ввод в линейный адрес в вашем векторе:

template <class T>
class matrix2D { 
     std::vector<T> data;
     int columns;
public:
     T &operator()(int x, int y) {
         return data[y * columns + x];
     }

     matrix2D(int x, int y) : data(x*y), columns(x) {}
};

Если по какой-либо причине вы хотите использовать адресацию в стиле matrix[a][b], вы можете использовать прокси-класс для обработки преобразования. Хотя это было для 3D-матрицы вместо 2D, я разместил демонстрацию этой техники в предыдущий ответ.

person Jerry Coffin    schedule 24.06.2011

Под капотом вектор может выглядеть примерно так (p-код):

class vector<T> {
    T      *data;
    size_t  s;
};

Теперь, если вы сделаете vector<vector<T> >, будет такой макет

vector<vector<T>> --> data {
    vector<T>,
    vector<T>,
    vector<T>
};

или в "встроенной" форме

vector<vector<T>> --> data {
    {data0, s0},
    {data1, s1},
    {data2, s2}
};

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

Стандарт требует, чтобы данные вектора были смежными, но не вектор в целом.

person Sebastian Mach    schedule 24.06.2011

Простой класс для создания, как вы его называете, 2D-массива, будет выглядеть примерно так:

template <class T> 2DArray {
private:
    T *m_data;
    int m_stride;
public:
    2DArray(int dimY, int dimX) : m_stride(dimX) : m_data(new[] T[dimX * dimY]) {}
    ~2DArray() { delete[] m_data; }
    T* operator[](int row) { return m_data + m_stride * row; }
}

Это можно использовать как:

2DArray<int> myArray(30,20);

for (int i = 0; i < 30; i++)
    for (int j = 0; j < 20; j++)
        myArray[i][j] = i + j;

Или даже передать &myArray[0][0] в качестве адреса низкоуровневым функциям, которые принимают своего рода «плоские буферы».

Но, как видите, наивные ожидания оборачиваются так, что это myarray[y][x].

Как правило, если вы взаимодействуете с кодом, который требует какого-то классического плоского массива в стиле C, то почему бы просто не использовать его?

Изменить: Как уже говорилось, приведенное выше просто. Никаких попыток проверки границ. Также как «массив».

person FrankH.    schedule 24.06.2011