Передать вложенный вектор C как встроенный в стиле многомерный массив

#c #vector #multidimensional-array #nested

#c #вектор #многомерный массив #вложенный

Вопрос:

Если у меня есть вектор на C , я знаю, что могу безопасно передать его как массив (указатель на содержащийся тип):

 void some_function(size_t size, int array[])
{
    // impl here...
}

// ...
std::vector<int> test;
some_function(test.size(), amp;test[0]);
  

Безопасно ли это делать с вложенным вектором?

 void some_function(size_t x, size_t y, size_t z, int* multi_dimensional_array)
{
    // impl here...
}

// ...

std::vector<std::vector<std::vector<int> > > test;
// initialize with non-jagged dimensions, ensure they're not empty, then...
some_function(test.size(), test[0].size(), test[0][0].size(), amp;test[0][0][0]);
  

Редактировать:

Если это небезопасно, каковы некоторые альтернативы, могу ли я изменить подпись some_function , и если я не могу?

Ответ №1:

Короткий ответ «нет».

Элементы здесь std::vector<std::vector<std::vector<int> > > test; не заменяются в непрерывной области памяти.

Ответ №2:

Вы можете ожидать, multi_dimensional_array что он будет указывать только на непрерывный блок памяти определенного размера test[0][0].size() * sizeof(int) . Но это, вероятно, не то, что вы хотите.

Комментарии:

1. Итак, в памяти я гарантированно буду иметь только количество смежных массивов X * Y размером / количеством Z (Z является самым внутренним измерением), но эти блоки не гарантированно будут смежными друг с другом?

2. @Merlyn Morgan-Graham: конструктор и push_back функция класса std::vector<T> обычно вызываются new T[] внутри. Когда T сам является векторным типом, это приведет к вызову конструктора по крайней мере один раз для каждого элемента массива, что приведет к последовательности вызовов new T[] для каждого из самых внутренних векторов. И если вы вызовете new T[] дважды, очень маловероятно (для большинства менеджеров памяти: совершенно невозможно) получить два смежных блока памяти.

Ответ №3:

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

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

Итак, как работает это волшебство? Когда больше нет внутреннего пространства для добавления следующего элемента в вектор, выделяется новое пространство, вдвое превышающее размер старого. Старое пространство копируется в новое, а старое пространство больше не требуется или допустимо, что приводит к зависанию любого указателя на старое пространство. Выделяется вдвое больше места, поэтому средняя стоимость вставки в вектор остается постоянной.

Комментарии:

1. Я полагаю, что стандарт обязывает это работать, из-за того, что вы сказали? Это при условии, что вы не измените вектор внутри вызова на some_function . Эта информация применима к первому вызову. Если это не так, дайте мне знать, хотя я думаю, что нашел ответы, подтверждающие, что верхний код работает на SO, при поиске дубликатов этого вопроса. На самом деле это 2-я часть моего примера кода, которая, как я подозреваю, может не сработать, и не полностью охвачена вашим ответом здесь.

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

Ответ №4:

Безопасно ли это делать с вложенным вектором?

Да, ЕСЛИ вы хотите получить доступ только к самому внутреннему вектору, и при условии, что вам известно количество элементов, которые он содержит, и вы не пытаетесь получить доступ к большему, чем это.

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

Альтернативой является то, что вы можете вызвать функцию some_function(size_t size, int array[]) для каждого самого внутреннего вектора (если это решит вашу проблему); и для этого вы можете выполнить этот трюк (или что-то подобное):

 void some_function(std::vector<int> amp; v1int)
{
    //the final call to some_function(size_t size, int array[]) 
    //which actually process the inner-most vectors
    some_function(v1int.size(), amp;v1int[0]);
}
void some_function(std::vector<std::vector<int> > amp; v2int)
{
    //call some_function(std::vector<int> amp; v1int) for each element!
    std::for_each(v2int.begin(), v2int.end(), some_function);
}

//call some_function(std::vector<std::vector<int> > amp; v2int) for each element!
std::for_each(test.begin(), test.end(), some_function);
  

Комментарии:

1. Что, если я захочу вызвать функцию void some_function(size_t x, size_t y, size_t z, int* multi_dimensional_array) ?

2. @Merlyn: Я уже говорил, что это невозможно, если вы хотите получить доступ ко всем трем измерениям в функции, потому что не гарантируется, что все самые внутренние векторы будут использовать непрерывную память; на самом деле, это очень маловероятно.

3. Правильно. Я также ищу простейшую альтернативу. Я думаю, что так и будет std::vector<int> array(x * y * z) , но я хотел бы посмотреть, есть ли у кого-нибудь идея получше или, по крайней мере, хорошее описание этого решения.

Ответ №5:

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

Это печально: вложенные векторы не являются хорошей практикой. Класс matrix, хранящий все в непрерывной памяти и управляющий доступом, действительно более эффективен и менее уродлив и, возможно, допускал бы что-то вроде T* matrix::get_raw() , но порядок содержимого все равно был бы деталью реализации.

Комментарии:

1. Существуют ли существующие реализации такой матрицы, о которых вы знаете?

2. @Merlyn К сожалению, в стандарте этого нет, и Boost.ublas, вероятно, излишен. codeproject.com/KB/architecture/ymatrix.aspx Выглядит красиво и довольно законченно. Однако я им не пользовался.

3. Это тоже выглядит неплохо. Вы им пользовались? boost.org/doc/libs/1_46_1/libs/multi_array/doc/user.html

4. @Merlyn: На самом деле в стандарте есть один, называемый valarray . Однако использовать его довольно сложно. Лично я просто вычисляю индекс, [ grid_size * z row_size * y x ] поскольку это действительно не так много работы. multi_array часто рекомендуется.

5. @Potatoswatter: Вау, я совсем забыл о valarray . Вероятно, по какой-то причине.

Ответ №6:

Простой ответ — нет, это не так. Вы пробовали это компилировать? И почему бы просто не передать весь 3D вектор в качестве ссылки? Если вы пытаетесь получить доступ к старому коду C таким образом, то у вас не получится.

Комментарии:

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

Ответ №7:

Было бы намного безопаснее передать вектор или ссылку на него:

 void some_function(std::vector<std::vector<std::vector<int>>> amp; vector);
  

Затем вы можете получить размер и элементы внутри функции, что снижает риск ошибок. Вы можете скопировать вектор или передать указатель / ссылку, в зависимости от ожидаемого размера и использования.

Если вам нужно передавать по модулям, тогда это становится немного сложнее.

Комментарии:

1. If you need to pass across modules, then it becomes slightly more complicated . Что будет задействовано? В идеале я мог бы использовать это для вызова существующего кода в стиле C.

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

Ответ №8:

Пытаться использовать amp;top_level_vector[0] и передавать это функции в стиле C, которая ожидает int* , небезопасно.

Для поддержки правильного доступа в стиле C к многомерному массиву все байты всей иерархии массивов должны быть непрерывными. В c std::vector это верно для элементов, содержащихся в векторе, но не для самого вектора. Если вы попытаетесь взять адрес вектора верхнего уровня, ala amp;top_level_vector[0] , вы получите массив векторов, а не массив int .

Векторная структура — это не просто массив содержащегося типа. Он реализован как структура, содержащая указатель, а также данные бухгалтерского учета размера и емкости. Следовательно, вопрос std::vector<std::vector<std::vector<int> > > представляет собой более или менее иерархическое дерево структур, сшитое вместе с указателями. Только конечные конечные узлы в этом дереве являются блоками смежных int значений. И каждый из этих блоков памяти не обязательно является смежным с каким-либо другим блоком.

Для того, чтобы взаимодействовать с C, вы можете передавать содержимое только одного vector . Таким образом, вам придется создать единый вектор std::vector<int> такого x * y * z размера. Или вы могли бы решить переструктурировать свой C-код, чтобы обрабатывать одну одномерную полосу данных за раз. Тогда вы могли бы сохранить иерархию и передавать только содержимое конечных векторов.

Комментарии:

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