2016-12-11 6 views
11

У меня есть следующий вид:Заменить вектор вектора с плоской структурой памяти

std::vector<std::vector<int>> indicies 

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

int *array_a = (int *) &(a[0][0]) 

Было бы хорошо, если новый тип имеет оператор [], так что я не должен изменить весь код. (Я мог бы сам реализовать его, если это необходимо). Мои идеи либо:

std::vector<std::array<int, 2>> 

или

std::vector<std::pair<int, int>> 

Как они выглядят в памяти? Я написал небольшой тест:

#include <iostream> 
#include <array> 
#include <vector> 
int main(int argc, char *argv[]) 
{ 
    using namespace std; 

    vector<array<int, 2>> a(100); 

    cout << sizeof(array<int, 2>) << endl; 

    for(auto i = 0; i < 10; i++){ 
     for(auto j = 0; j < 2; j++){ 
      cout << "a[" << i << "][" << j << "] " 
       <<&(a[i][j]) << endl; 
     } 
    } 
    return 0; 
} 

, что приводит к:

8 
a[0][0] 0x1b72c20 
a[0][1] 0x1b72c24 
a[1][0] 0x1b72c28 
a[1][1] 0x1b72c2c 
a[2][0] 0x1b72c30 
a[2][1] 0x1b72c34 
a[3][0] 0x1b72c38 
a[3][1] 0x1b72c3c 
a[4][0] 0x1b72c40 
a[4][1] 0x1b72c44 
a[5][0] 0x1b72c48 
a[5][1] 0x1b72c4c 
a[6][0] 0x1b72c50 
a[6][1] 0x1b72c54 
a[7][0] 0x1b72c58 
a[7][1] 0x1b72c5c 
a[8][0] 0x1b72c60 
a[8][1] 0x1b72c64 
a[9][0] 0x1b72c68 
a[9][1] 0x1b72c6c 

Это, кажется, работает в этом случае. Это поведение в стандарте или просто удачное совпадение? Есть лучший способ сделать это?

+0

[Элементы вектора хранятся смежно] (http://stackoverflow.com/q/849168/238902) – Default

+3

Я думаю, что вопрос: Может ли быть заполнение в 'std :: pairs' и' std: : arrays'? Просто, что 'std :: vector' сохраняет свои элементы смежно, здесь недостаточно. – Wintermute

+0

Вектор векторов ** не гарантированно сохраняет элементы смежно **. Только объекты (внутренние векторы как адреса или любое другое представление используются) хранятся смежно, но не данные, на которые указывает каждый отдельный указатель данных внутреннего вектора. – vsoftco

ответ

2

array<int,2> будет структурой, содержащей массив int[2]; стандарт не дает прямого мандата, но на самом деле нет другого разумного и практичного способа сделать это.

См. 23.3.7 [массив] в пределах стандарта. В стандарте, который я могу найти, нет ничего, что требует sizeof(std::array<char, 10>)==1024 как ложного. Это было бы смехотворным QOI (качество реализации); каждая реализация, которую я видел, имеет sizeof(std::array<T,N>) == N*sizeof(T), и все остальное, что я считаю враждебным.

Массивы должны быть смежными контейнерами, которые могут быть инициализированы до N аргументов типов, конвертируемых в T.

Стандарт допускает заполнение после такого массива. Я знаю 0 компиляторов, которые вставляют такие дополнения.

Буфер со смежным std::array<int,2> не гарантированно надежно защищен как плоский буфер int. На самом деле правила псевдонимов почти наверняка запрещают такой доступ, как неопределенное поведение. Вы даже не можете сделать это с помощью int[3][7]! See this SO question and answer, and here, и here.

Большинство компиляторов сделают то, что вы описываете, но оптимизатор может решить, что доступ через int* и через array<int,2>* не может получить доступ к одной и той же памяти и создать безумные результаты. Это не похоже на это.

Подход, основанный на стандартах, заключался бы в том, чтобы написать тип вида массива (который принимает два указателя и формирует итерируемый диапазон с перегрузкой []). Затем напишите 2d-представление плоского буфера, нижний размер которого будет либо временем выполнения, либо значением времени компиляции. Его [] затем вернет представление массива.

Будет создан код в boost и другие библиотеки «стандартного расширения», чтобы сделать это за вас.

Объедините 2-мерный вид с типом, владеющим вектором, и вы получите свой 2-й вектор.

Единственное отличие заключается в том, что когда старый вектор векторного кода копирует нижнее измерение (например, auto inner=outer[i]), он копирует данные, а вместо этого создает представление.

+0

IMO разрешено псевдонизировать структуру, состоящую из 'int [2]' as ints –

+0

@mm Но не структура, содержащая 2 таких массива (совместимость макета/тот же адрес относится к первому член), а также массив из 2 таких структур, как один буфер 'int'. Итак, 'int * ptr = ?; ptr [3]; '* не может * ссылаться на элемент любого' array 'определенным образом в соответствии со стандартом, независимо от того, что такое' ''. – Yakk

+0

Стандарт требует, чтобы массив не имел прокладки между его элементами. Это рекурсивно применяется к массивам массивов. Макет int [4] и int [2] [2] и массив [2] по определению один и тот же. –

-4

Насколько я знаю, std::vector смежны в памяти. Взгляните на эти вопросы:

Why is std::vector contiguous?,

Are std::vector elements guaranteed to be contiguous?

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

Если вы хотите реализовать структуру, которая всегда граничит с первого элемента первого вектора до последнего элемента последнего вектора, вы можете реализовать его как пользовательский класс, который имеет vector<int> и elems_per_vector, что указывает на то, количество элементов в каждом внутреннем векторе.

Затем вы можете перегрузить operator(), поэтому для доступа к a(i,j) вы фактически обращаетесь к a.vector[a.elems_per_vector*i+j]. Однако, чтобы вставлять новые элементы и поддерживать постоянный размер внутренних векторов между ними, вам нужно будет сделать столько вложений, сколько у вас внутренних векторов.

+0

Astd :: vector' сохраняет свои элементы смежно, но не локально. То есть в векторе векторов каждый внутренний вектор сохраняет свои элементы смежно, но последний элемент одного внутреннего вектора обычно не будет храниться последовательно с первым элементом следующего внутреннего вектора, что и требует OP. – Wintermute

+1

@J. Checa * Если вы используете вектор векторов, вы должны иметь полностью смежную структуру * Это ложь. Вектор векторов действительно хранит внутренние векторы (как объекты) смежно. Однако каждый внутренний вектор хранит внутренне указатель, который представляет свои данные. Сами указатели отнюдь не гарантированно указывают на смежные области памяти. – vsoftco

+0

Извините, я неправильно понял вопрос OP. Кажется, он хочет, чтобы все элементы, начиная с первого элемента первого вектора, до последнего элемента последнего вектора, были выровнены в памяти. Да, действительно, если вы создаете вектор векторов, вы получаете непрерывную зону памяти с указателями на внутренние векторы. Кроме того, мое последнее утверждение привело к путанице, я хотел сказать, что все ваши элементы будут смежными, ссылаясь на самих указателей. Я отредактирую свой ответ, чтобы избежать путаницы. Спасибо @vsoftco и Wintermute за ваши комментарии: D –

2

Есть ли лучший способ сделать это?

Я недавно закончил еще одну версию игры в жизни.

Игровая доска 2d, и да, вектор векторов потратил впустую пространство в ней.

В моих недавних усилиях я решил попробовать 1-й вектор для 2-го игрового поля.

typedef std::vector<Cell_t*> GameBoard_t; 

Затем я создал простую функцию индексации, для того, когда использование строк/COL добавляется к читаемости кода:

inline size_t gbIndx(int row, int col) 
    { return ((row * MAXCOL) + col); } 

Пример: доступ к строке 27, столбец 33:

Cell_t* cell = gameBoard[ gbIndx(27, 33) ]; 

Все Cell_t * в gameBoard теперь упакованы в обратную сторону (определение вектора) и тривиальны для доступа (инициализация, отображение и т. Д.) В порядке строки/col, используя gbIndx().


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

void setAliveRandom(const GameBoard_t& gameBoard) 
{ 
    GameBoard_t myVec(m_gameBoard); // copy cell vector 

    time_t seed = std::chrono::system_clock:: 
     now().time_since_epoch().count(); 

    // randomize copy's element order 
    std::shuffle (myVec.begin(), myVec.end(), std::default_random_engine(seed)); 

    int count = 0; 
    for (auto it : myVec) 
    { 
     if (count & 1) it->setAlive(); // touch odd elements 
     count += 1; 
    } 
} 

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

+0

g ++ v6.2 говорит мне, что «std :: default_random_engine (...)» - это реализация. Теперь я использую std :: mt19937_64 gen (rd) с std :: random_device rd; –

Смежные вопросы